zcommon/src/main/java/org/zkoss/lang/Objects.java

Summary

Maintainability
F
1 wk
Test Coverage
/* Objects.java


    Purpose: Utilities related to Object.
    Description:
    History:
     2001/5/12, Tom M. Yeh: Created.


Copyright (C) 2001 Potix Corporation. All Rights Reserved.

{{IS_RIGHT
    This program is distributed under LGPL Version 2.1 in the hope that
    it will be useful, but WITHOUT ANY WARRANTY.
}}IS_RIGHT
*/
package org.zkoss.lang;

import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.rmi.MarshalledObject;
import java.util.Date;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.zkoss.text.DateFormats;
import org.zkoss.util.ArraysX;

/**
 * Utilities related to the Object class.
 *
 * @author tomyeh
 */
public class Objects {
    private static final Logger log = LoggerFactory.getLogger(Objects.class);

    /** Denotes unknown. It is useful if both null and unknown is required.
     */
    public static final Object UNKNOWN = new Object() { //anonymous class
            public final String toString() {
                return "(null)"; //a little different from null
            }
        };

    /** The zero long.
     */
    public static final Long ZERO_LONG = 0L;
    /** The zero integer.
     */
    public static final Integer ZERO_INTEGER = 0;
    /** The zero short.
     */
    public static final Short ZERO_SHORT = (short) 0;
    /** The zero integer.
     */
    public static final Byte ZERO_BYTE = (byte) 0;
    /** The zero float.
     */
    public static final Float ZERO_FLOAT = 0F;
    /** The zero double.
     */
    public static final Double ZERO_DOUBLE = 0D;
    /** Represents 0 in big decimal.
     * The same as {@link org.zkoss.math.BigDecimals#ZERO}.
     * @see org.zkoss.math.BigDecimals#ONE
     */
    public static final BigDecimal ZERO_BIG_DECIMAL = BigDecimal.valueOf(0D);
    /** Represents 0 in big integer.
     * Same as {@link org.zkoss.math.BigIntegers#ZERO}.
     */
    public static final BigInteger ZERO_BIG_INTEGER = BigInteger.ZERO;
    /** The null character.
     */
    public static final Character NULL_CHARACTER = '\u0000';

    /**
     * Returns the next hash value by giving the previous one and a new one.
     * The caller usually uses a loop to accumulate all related fields.
     *
     * @param prevHashVal the previous hash value returned by this method; 0
     * if it is the first call.
     * @param newVal the new value to put in
     * @return the new hash value
     */
    public static final int nextHashCode(int prevHashVal, int newVal) {
        return prevHashVal*31 + newVal;
    }

    /**
     * Generates hash codes for an array of boolean.
     * It is suggested to cache the hash code.
     *
     * @param v the array
     * @return the hash code
     */
    public static final int hashCode(boolean[] v) {
        int h = 1; //not to return 0 if possible, so caller cache it easily
        for (int j=v.length; --j>=0;)
            h = nextHashCode(h, v[j] ? 1: 0);
        return h;
    }
    /**
     * Generates hash codes for an array of bytes.
     * It is suggested to cache the hash code.
     *
     * @param v the array
     * @return the hash code
     */
    public static final int hashCode(byte[] v) {
        int h = 1; //not to return 0 if possible, so caller cache it easily
        for (int j=v.length; --j>=0;)
            h = nextHashCode(h, v[j]);
        return h;
    }
    /** 
     * Generates hash codes for an array of bytes up to the specified length.
     * It is suggested to cache the hash code.
     *
     * @param v the array
     * @param len the maximal length to generate hashCode
     * @return the hash code
     */
    public static final int hashCode(byte[] v, int len) {
        int h = 1; //not to return 0 if possible, so caller cache it easily
        if (len > v.length) len = v.length;
        for (int j=len; --j>=0;)
            h = nextHashCode(h, v[j]);
        return h;
    }
    /**
     * Generates hash codes for an array.
     * It is suggested to cache the hash code.
     *
     * @param v the array
     * @return the hash code
     */
    public static final int hashCode(char[] v) {
        int h = 1; //not to return 0 if possible, so caller cache it easily
        for (int j=v.length; --j>=0;)
            h = nextHashCode(h, v[j]);
        return h;
    }
    /**
     * Generates hash codes for an array.
     * It is suggested to cache the hash code.
     *
     * @param v the array
     * @return the hash code
     */
    public static final int hashCode(short[] v) {
        int h = 1; //not to return 0 if possible, so caller cache it easily
        for (int j=v.length; --j>=0;)
            h = nextHashCode(h, v[j]);
        return h;
    }
    /**
     * Generates hash codes for an array.
     * It is suggested to cache the hash code.
     *
     * @param v the byte array
     * @return the hash code
     */
    public static final int hashCode(int[] v) {
        int h = 1; //not to return 0 if possible, so caller cache it easily
        for (int j=v.length; --j>=0;)
            h = nextHashCode(h, v[j]);
        return h;
    }
    /**
     * Generates hash codes for an array.
     * It is suggested to cache the hash code.
     *
     * @param v the array
     * @return the hash code
     */
    public static final int hashCode(long[] v) {
        int h = 1; //not to return 0 if possible, so caller cache it easily
        for (int j=v.length; --j>=0;) {
            h = nextHashCode(h, (int)v[j]);
            h = nextHashCode(h, (int)(v[j]>>32));
        }
        return h;
    }
    /** Returns the object's hash code, or zero if null. */
    public static final int hashCode(Object o) {
        return o==null ? 0: o.hashCode();
    }
    /**
     * Tests whether two objects are equals.
     *
     * <p>It takes care of the null case. Thus, it is helpful to implement
     * Object.equals.
     *
     * <p>Notice: it uses compareTo if BigDecimal is found. So, in this case,
     * a.equals(b) might not be the same as Objects.equals(a, b).
     *
     * <p>If both a and b are Object[], they are compared item-by-item.
     */
    public static final boolean equals(Object a, Object b) {
        if (a == b || (a != null && b != null && a.equals(b)))
            return true;
        if ((a instanceof BigDecimal) && (b instanceof BigDecimal))
            return ((BigDecimal)a).compareTo((BigDecimal) b) == 0;

        if (a == null || !a.getClass().isArray())
            return false;

        if ((a instanceof Object[]) && (b instanceof Object[])) {
            final Object[] as = (Object[])a;
            final Object[] bs = (Object[])b;
            if (as.length != bs.length)
                return false;
            for (int j = as.length; --j >=0;)
                if (!equals(as[j], bs[j])) //recursive
                    return false;
            return true;
        }
        if ((a instanceof int[]) && (b instanceof int[])) {
            final int[] as = (int[])a;
            final int[] bs = (int[])b;
            if (as.length != bs.length)
                return false;
            for (int j = as.length; --j >=0;)
                if (as[j] != bs[j])
                    return false;
            return true;
        }
        if ((a instanceof byte[]) && (b instanceof byte[])) {
            final byte[] as = (byte[])a;
            final byte[] bs = (byte[])b;
            if (as.length != bs.length)
                return false;
            for (int j = as.length; --j >=0;)
                if (as[j] != bs[j])
                    return false;
            return true;
        }
        if ((a instanceof char[]) && (b instanceof char[])) {
            final char[] as = (char[])a;
            final char[] bs = (char[])b;
            if (as.length != bs.length)
                return false;
            for (int j = as.length; --j >=0;)
                if (as[j] != bs[j])
                    return false;
            return true;
        }
        if ((a instanceof long[]) && (b instanceof long[])) {
            final long[] as = (long[])a;
            final long[] bs = (long[])b;
            if (as.length != bs.length)
                return false;
            for (int j = as.length; --j >=0;)
                if (as[j] != bs[j])
                    return false;
            return true;
        }
        if ((a instanceof short[]) && (b instanceof short[])) {
            final short[] as = (short[])a;
            final short[] bs = (short[])b;
            if (as.length != bs.length)
                return false;
            for (int j = as.length; --j >=0;)
                if (as[j] != bs[j])
                    return false;
            return true;
        }
        if ((a instanceof double[]) && (b instanceof double[])) {
            final double[] as = (double[])a;
            final double[] bs = (double[])b;
            if (as.length != bs.length)
                return false;
            for (int j = as.length; --j >=0;)
                if (Double.compare(as[j], bs[j]) != 0)
                    return false;
            return true;
        }
        if ((a instanceof float[]) && (b instanceof float[])) {
            final float[] as = (float[])a;
            final float[] bs = (float[])b;
            if (as.length != bs.length)
                return false;
            for (int j = as.length; --j >=0;)
                if (Float.compare(as[j], bs[j]) != 0)
                    return false;
            return true;
        }
        if ((a instanceof boolean[]) && (b instanceof boolean[])) {
            final boolean[] as = (boolean[])a;
            final boolean[] bs = (boolean[])b;
            if (as.length != bs.length)
                return false;
            for (int j = as.length; --j >=0;)
                if (as[j] != bs[j])
                    return false;
            return true;
        }
        return false;
    }

    /**
     * Converts any object to a character array.
     *
     * @param o the object to convert
     * @return the char array or null if o is null
     */
    public static final char[] toCharArray(Object o) {
        if (o == null)
            return null;
        if (o instanceof char[])
            return (char[])o;

        if (o instanceof String)
            return ((String)o).toCharArray();

        try {
            Method m = o.getClass().getMethod("toCharArray");
            return (char[])m.invoke(o);
        } catch (Exception ex) {
            return (o.toString()).toCharArray();
        }
    }
    /**
     * Converts any object to a string.
     * If o is an object array, it invokes {@link ArraysX#toString}
     * to make the string more readable.
     */
    public static final String toString(Object o) {
        if (o == null) return null;
        if (o instanceof Date) return DateFormats.format((Date)o, false);
        if (o instanceof Class) {
            final Class cls = (Class)o;
            final String clsnm = cls.getName();
            if (!clsnm.startsWith("$Proxy"))
                return "class "+clsnm;

            final Class[] ifs = cls.getInterfaces();
            switch (ifs.length) {
            case 0:
                return "class "+clsnm;
            case 1:
                return "proxy "+Objects.toString(ifs[0]);
            default:
                return "proxy "+Objects.toString(ifs);
            }
        }
        if (o.getClass().isArray()) {
            if (o instanceof Object[])
                return ArraysX.toString((Object[])o);
            if (o instanceof int[])
                return ArraysX.toString((int[])o);
            if (o instanceof short[])
                return ArraysX.toString((short[])o);
            if (o instanceof long[])
                return ArraysX.toString((long[])o);
            if (o instanceof double[])
                return ArraysX.toString((double[])o);
            if (o instanceof byte[])
                return ArraysX.toString((byte[])o);
            if (o instanceof boolean[])
                return ArraysX.toString((boolean[])o);
            if (o instanceof char[])
                return ArraysX.toString((char[])o);
            if (o instanceof float[])
                return ArraysX.toString((float[])o);
        }
        return o.toString();
     }

    /** Converts an integer to a big-endian byte array.
     */
    public static final byte[] toByteArray(int v) {
        return new byte[] {
            (byte)(v>>>24), (byte)(v>>>16), (byte)(v>>>8), (byte)v};
    }
    /** Converts a long to a big-endian byte array.
     */
    public static final byte[] toByteArray(long v) {
        return new byte[] {
            (byte)(v>>>56), (byte)(v>>>48), (byte)(v>>>40), (byte)(v>>>32),
            (byte)(v>>>24), (byte)(v>>>16), (byte)(v>>>8),  (byte)v};
    }
    /** Converts a short to a big-endian byte array.
     */
    public static final byte[] toByteArray(short v) {
        return new byte[] {(byte)(v>>>8), (byte)v};
    }
    /** Converts a byte to a big-endian byte array.
     */
    public static final byte[] toByteArray(byte v) {
        return new byte[] {v};
    }

    /**
     * Clones the specified object. Use clone() if Cloneable.
     * Otherwise, try to serialize/deserialize it by use of MarshalledObject.
     *
     * <p>If o is null, null is returned.
     *
     * @exception SystemException if failed to clone
     */
    public static final Object clone(Object o) {
        if (o == null)
            return o;

        try {
            final Class<?> kls = o.getClass();
            if (kls.isArray())
                return ArraysX.duplicate(o);

            if (o instanceof Cloneable) {
                try {
                    return kls.getMethod("clone").invoke(o);
                } catch (NoSuchMethodException ex) {
                    if (log.isDebugEnabled()) log.debug("No clone() for "+kls);
                }
            }

            //:TODO: MarshalledObject is said with very bad performance, change it
            //if exists other good deep clone method.
            return new MarshalledObject<Object>(o).get();
        } catch (Exception ex) {
            throw SystemException.Aide.wrap(ex);
        }
    }
}