CompareUtil.java

package zserio.runtime;

import java.math.BigInteger;

import zserio.runtime.array.Array;
import zserio.runtime.io.BitBuffer;

/**
 * Utilities for ordering of Zserio objects.
 *
 * All compare methods return a negative integer, zero, or a positive integer as the first value is less than,
 * equal to, or greater than the second value. A null value is less than any non-null value.
 */
public final class CompareUtil
{
    /**
     * Compares two boolean values, false is less than true.
     *
     * @param value1 First value to compare.
     * @param value2 Second value to compare.
     *
     * @return Result of the comparison.
     */
    public static int compare(boolean value1, boolean value2)
    {
        return Boolean.compare(value1, value2);
    }

    /**
     * Compares two Boolean values, false is less than true.
     *
     * @param value1 First value to compare.
     * @param value2 Second value to compare.
     *
     * @return Result of the comparison.
     */
    public static int compare(Boolean value1, Boolean value2)
    {
        if (value1 == null || value2 == null)
            return compareNulls(value1, value2);

        return compare(value1.booleanValue(), value2.booleanValue());
    }

    /**
     * Compares two byte values.
     *
     * @param value1 First value to compare.
     * @param value2 Second value to compare.
     *
     * @return Result of the comparison.
     */
    public static int compare(byte value1, byte value2)
    {
        return Byte.compare(value1, value2);
    }

    /**
     * Compares two Byte values.
     *
     * @param value1 First value to compare.
     * @param value2 Second value to compare.
     *
     * @return Result of the comparison.
     */
    public static int compare(Byte value1, Byte value2)
    {
        if (value1 == null || value2 == null)
            return compareNulls(value1, value2);

        return compare(value1.byteValue(), value2.byteValue());
    }

    /**
     * Compares two short values.
     *
     * @param value1 First value to compare.
     * @param value2 Second value to compare.
     *
     * @return Result of the comparison.
     */
    public static int compare(short value1, short value2)
    {
        return Short.compare(value1, value2);
    }

    /**
     * Compares two Short values.
     *
     * @param value1 First value to compare.
     * @param value2 Second value to compare.
     *
     * @return Result of the comparison.
     */
    public static int compare(Short value1, Short value2)
    {
        if (value1 == null || value2 == null)
            return compareNulls(value1, value2);

        return compare(value1.shortValue(), value2.shortValue());
    }

    /**
     * Compares two int values.
     *
     * @param value1 First value to compare.
     * @param value2 Second value to compare.
     *
     * @return Result of the comparison.
     */
    public static int compare(int value1, int value2)
    {
        return Integer.compare(value1, value2);
    }

    /**
     * Compares two Integer values.
     *
     * @param value1 First value to compare.
     * @param value2 Second value to compare.
     *
     * @return Result of the comparison.
     */
    public static int compare(Integer value1, Integer value2)
    {
        if (value1 == null || value2 == null)
            return compareNulls(value1, value2);

        return compare(value1.intValue(), value2.intValue());
    }

    /**
     * Compares two long values.
     *
     * @param value1 First value to compare.
     * @param value2 Second value to compare.
     *
     * @return Result of the comparison.
     */
    public static int compare(long value1, long value2)
    {
        return Long.compare(value1, value2);
    }

    /**
     * Compares two Long values.
     *
     * @param value1 First value to compare.
     * @param value2 Second value to compare.
     *
     * @return Result of the comparison.
     */
    public static int compare(Long value1, Long value2)
    {
        if (value1 == null || value2 == null)
            return compareNulls(value1, value2);

        return compare(value1.longValue(), value2.longValue());
    }

    /**
     * Compares two float values using Float.compare() which is consistent with equals().
     *
     * @param value1 First value to compare.
     * @param value2 Second value to compare.
     *
     * @return Result of the comparison.
     */
    public static int compare(float value1, float value2)
    {
        return Float.compare(value1, value2);
    }

    /**
     * Compares two Float values using Float.compare() which is consistent with equals().
     *
     * @param value1 First value to compare.
     * @param value2 Second value to compare.
     *
     * @return Result of the comparison.
     */
    public static int compare(Float value1, Float value2)
    {
        if (value1 == null || value2 == null)
            return compareNulls(value1, value2);

        return compare(value1.floatValue(), value2.floatValue());
    }

    /**
     * Compares two double values using Double.compare() which is consistent with equals().
     *
     * @param value1 First value to compare.
     * @param value2 Second value to compare.
     *
     * @return Result of the comparison.
     */
    public static int compare(double value1, double value2)
    {
        return Double.compare(value1, value2);
    }

    /**
     * Compares two Double values using Double.compare() which is consistent with equals().
     *
     * @param value1 First value to compare.
     * @param value2 Second value to compare.
     *
     * @return Result of the comparison.
     */
    public static int compare(Double value1, Double value2)
    {
        if (value1 == null || value2 == null)
            return compareNulls(value1, value2);

        return compare(value1.doubleValue(), value2.doubleValue());
    }

    /**
     * Compares two BigInteger values.
     *
     * @param value1 First value to compare.
     * @param value2 Second value to compare.
     *
     * @return Result of the comparison.
     */
    public static int compare(BigInteger value1, BigInteger value2)
    {
        if (value1 == null || value2 == null)
            return compareNulls(value1, value2);

        return value1.compareTo(value2);
    }

    /**
     * Compares two String values by Unicode code points.
     *
     * This gives the same order as the lexicographical compare of the UTF-8 encoded strings.
     *
     * @param value1 First value to compare.
     * @param value2 Second value to compare.
     *
     * @return Result of the comparison.
     */
    public static int compare(String value1, String value2)
    {
        if (value1 == null || value2 == null)
            return compareNulls(value1, value2);

        int index1 = 0;
        int index2 = 0;
        while (index1 < value1.length() && index2 < value2.length())
        {
            final int codePoint1 = value1.codePointAt(index1);
            final int codePoint2 = value2.codePointAt(index2);
            if (codePoint1 != codePoint2)
                return Integer.compare(codePoint1, codePoint2);

            index1 += Character.charCount(codePoint1);
            index2 += Character.charCount(codePoint2);
        }

        return Boolean.compare(index1 < value1.length(), index2 < value2.length());
    }

    /**
     * Compares two bytes values lexicographically, bytes are compared as unsigned values.
     *
     * @param value1 First value to compare.
     * @param value2 Second value to compare.
     *
     * @return Result of the comparison.
     */
    public static int compare(byte[] value1, byte[] value2)
    {
        if (value1 == null || value2 == null)
            return compareNulls(value1, value2);

        final int minLength = Math.min(value1.length, value2.length);
        for (int i = 0; i < minLength; ++i)
        {
            final int result = Integer.compare(value1[i] & 0xFF, value2[i] & 0xFF);
            if (result != 0)
                return result;
        }

        return Integer.compare(value1.length, value2.length);
    }

    /**
     * Compares two BitBuffer values.
     *
     * @param value1 First value to compare.
     * @param value2 Second value to compare.
     *
     * @return Result of the comparison.
     */
    public static int compare(BitBuffer value1, BitBuffer value2)
    {
        if (value1 == null || value2 == null)
            return compareNulls(value1, value2);

        return value1.compareTo(value2);
    }

    /**
     * Compares two ZserioEnum values by their underlying values.
     *
     * Note that this differs from the natural ordering of Java enums which uses the declaration order.
     *
     * @param value1 First value to compare.
     * @param value2 Second value to compare.
     * @param <T> Concrete Java type implementing ZserioEnum.
     *
     * @return Result of the comparison.
     */
    public static <T extends ZserioEnum & SizeOf> int compare(T value1, T value2)
    {
        if (value1 == null || value2 == null)
            return compareNulls(value1, value2);

        return compareEnumValues(value1, value2);
    }

    /**
     * Compares two SizeOf values using their natural ordering.
     *
     * Note: This is intended to be used by generated objects (including ZserioBitmask) which implement
     * Comparable.
     *
     * @param value1 First value to compare.
     * @param value2 Second value to compare.
     * @param <T> Concrete Java type implementing SizeOf and Comparable.
     *
     * @return Result of the comparison.
     */
    @SuppressWarnings("unchecked")
    public static <T extends SizeOf> int compare(T value1, T value2)
    {
        if (value1 == null || value2 == null)
            return compareNulls(value1, value2);

        return ((Comparable<T>)value1).compareTo(value2);
    }

    /**
     * Compares two Array values lexicographically.
     *
     * @param value1 First value to compare.
     * @param value2 Second value to compare.
     *
     * @return Result of the comparison.
     */
    public static int compare(Array value1, Array value2)
    {
        if (value1 == null || value2 == null)
            return compareNulls(value1, value2);

        return value1.compareTo(value2);
    }

    /**
     * Compares two boolean raw arrays lexicographically.
     *
     * @param value1 First value to compare.
     * @param value2 Second value to compare.
     *
     * @return Result of the comparison.
     */
    public static int compare(boolean[] value1, boolean[] value2)
    {
        if (value1 == null || value2 == null)
            return compareNulls(value1, value2);

        final int minLength = Math.min(value1.length, value2.length);
        for (int i = 0; i < minLength; ++i)
        {
            final int result = compare(value1[i], value2[i]);
            if (result != 0)
                return result;
        }

        return Integer.compare(value1.length, value2.length);
    }

    /**
     * Compares two short raw arrays lexicographically.
     *
     * @param value1 First value to compare.
     * @param value2 Second value to compare.
     *
     * @return Result of the comparison.
     */
    public static int compare(short[] value1, short[] value2)
    {
        if (value1 == null || value2 == null)
            return compareNulls(value1, value2);

        final int minLength = Math.min(value1.length, value2.length);
        for (int i = 0; i < minLength; ++i)
        {
            final int result = compare(value1[i], value2[i]);
            if (result != 0)
                return result;
        }

        return Integer.compare(value1.length, value2.length);
    }

    /**
     * Compares two int raw arrays lexicographically.
     *
     * @param value1 First value to compare.
     * @param value2 Second value to compare.
     *
     * @return Result of the comparison.
     */
    public static int compare(int[] value1, int[] value2)
    {
        if (value1 == null || value2 == null)
            return compareNulls(value1, value2);

        final int minLength = Math.min(value1.length, value2.length);
        for (int i = 0; i < minLength; ++i)
        {
            final int result = compare(value1[i], value2[i]);
            if (result != 0)
                return result;
        }

        return Integer.compare(value1.length, value2.length);
    }

    /**
     * Compares two long raw arrays lexicographically.
     *
     * @param value1 First value to compare.
     * @param value2 Second value to compare.
     *
     * @return Result of the comparison.
     */
    public static int compare(long[] value1, long[] value2)
    {
        if (value1 == null || value2 == null)
            return compareNulls(value1, value2);

        final int minLength = Math.min(value1.length, value2.length);
        for (int i = 0; i < minLength; ++i)
        {
            final int result = compare(value1[i], value2[i]);
            if (result != 0)
                return result;
        }

        return Integer.compare(value1.length, value2.length);
    }

    /**
     * Compares two float raw arrays lexicographically.
     *
     * @param value1 First value to compare.
     * @param value2 Second value to compare.
     *
     * @return Result of the comparison.
     */
    public static int compare(float[] value1, float[] value2)
    {
        if (value1 == null || value2 == null)
            return compareNulls(value1, value2);

        final int minLength = Math.min(value1.length, value2.length);
        for (int i = 0; i < minLength; ++i)
        {
            final int result = compare(value1[i], value2[i]);
            if (result != 0)
                return result;
        }

        return Integer.compare(value1.length, value2.length);
    }

    /**
     * Compares two double raw arrays lexicographically.
     *
     * @param value1 First value to compare.
     * @param value2 Second value to compare.
     *
     * @return Result of the comparison.
     */
    public static int compare(double[] value1, double[] value2)
    {
        if (value1 == null || value2 == null)
            return compareNulls(value1, value2);

        final int minLength = Math.min(value1.length, value2.length);
        for (int i = 0; i < minLength; ++i)
        {
            final int result = compare(value1[i], value2[i]);
            if (result != 0)
                return result;
        }

        return Integer.compare(value1.length, value2.length);
    }

    /**
     * Compares two BigInteger raw arrays lexicographically.
     *
     * @param value1 First value to compare.
     * @param value2 Second value to compare.
     *
     * @return Result of the comparison.
     */
    public static int compare(BigInteger[] value1, BigInteger[] value2)
    {
        if (value1 == null || value2 == null)
            return compareNulls(value1, value2);

        final int minLength = Math.min(value1.length, value2.length);
        for (int i = 0; i < minLength; ++i)
        {
            final int result = compare(value1[i], value2[i]);
            if (result != 0)
                return result;
        }

        return Integer.compare(value1.length, value2.length);
    }

    /**
     * Compares two bytes raw arrays lexicographically.
     *
     * @param value1 First value to compare.
     * @param value2 Second value to compare.
     *
     * @return Result of the comparison.
     */
    public static int compare(byte[][] value1, byte[][] value2)
    {
        if (value1 == null || value2 == null)
            return compareNulls(value1, value2);

        final int minLength = Math.min(value1.length, value2.length);
        for (int i = 0; i < minLength; ++i)
        {
            final int result = compare(value1[i], value2[i]);
            if (result != 0)
                return result;
        }

        return Integer.compare(value1.length, value2.length);
    }

    /**
     * Compares two String raw arrays lexicographically.
     *
     * @param value1 First value to compare.
     * @param value2 Second value to compare.
     *
     * @return Result of the comparison.
     */
    public static int compare(String[] value1, String[] value2)
    {
        if (value1 == null || value2 == null)
            return compareNulls(value1, value2);

        final int minLength = Math.min(value1.length, value2.length);
        for (int i = 0; i < minLength; ++i)
        {
            final int result = compare(value1[i], value2[i]);
            if (result != 0)
                return result;
        }

        return Integer.compare(value1.length, value2.length);
    }

    /**
     * Compares two BitBuffer raw arrays lexicographically.
     *
     * @param value1 First value to compare.
     * @param value2 Second value to compare.
     *
     * @return Result of the comparison.
     */
    public static int compare(BitBuffer[] value1, BitBuffer[] value2)
    {
        if (value1 == null || value2 == null)
            return compareNulls(value1, value2);

        final int minLength = Math.min(value1.length, value2.length);
        for (int i = 0; i < minLength; ++i)
        {
            final int result = compare(value1[i], value2[i]);
            if (result != 0)
                return result;
        }

        return Integer.compare(value1.length, value2.length);
    }

    /**
     * Compares two ZserioEnum raw arrays lexicographically.
     *
     * @param value1 First value to compare.
     * @param value2 Second value to compare.
     * @param <T> Concrete Java type implementing ZserioEnum.
     *
     * @return Result of the comparison.
     */
    public static <T extends ZserioEnum & SizeOf> int compare(T[] value1, T[] value2)
    {
        if (value1 == null || value2 == null)
            return compareNulls(value1, value2);

        final int minLength = Math.min(value1.length, value2.length);
        for (int i = 0; i < minLength; ++i)
        {
            final int result = compare(value1[i], value2[i]);
            if (result != 0)
                return result;
        }

        return Integer.compare(value1.length, value2.length);
    }

    /**
     * Compares two SizeOf raw arrays lexicographically.
     *
     * @param value1 First value to compare.
     * @param value2 Second value to compare.
     * @param <T> Concrete Java type implementing SizeOf and Comparable.
     *
     * @return Result of the comparison.
     */
    public static <T extends SizeOf> int compare(T[] value1, T[] value2)
    {
        if (value1 == null || value2 == null)
            return compareNulls(value1, value2);

        final int minLength = Math.min(value1.length, value2.length);
        for (int i = 0; i < minLength; ++i)
        {
            final int result = compare(value1[i], value2[i]);
            if (result != 0)
                return result;
        }

        return Integer.compare(value1.length, value2.length);
    }

    /**
     * Compares two objects of any type which can be stored in a Zserio object.
     *
     * Note: This is intended to be used by generated choices when no case matches the selector.
     * Objects of different classes are ordered by their class names.
     *
     * @param value1 First value to compare.
     * @param value2 Second value to compare.
     *
     * @return Result of the comparison.
     */
    @SuppressWarnings("unchecked")
    public static int compareObjects(Object value1, Object value2)
    {
        if (value1 == null || value2 == null)
            return compareNulls(value1, value2);

        if (value1.getClass() != value2.getClass())
            return value1.getClass().getName().compareTo(value2.getClass().getName());

        if (value1 instanceof String)
            return compare((String)value1, (String)value2);
        if (value1 instanceof byte[])
            return compare((byte[])value1, (byte[])value2);
        if (value1 instanceof ZserioEnum)
            return compareEnumValues((ZserioEnum)value1, (ZserioEnum)value2);

        return ((Comparable<Object>)value1).compareTo(value2);
    }

    private static int compareEnumValues(ZserioEnum value1, ZserioEnum value2)
    {
        final Number enumValue1 = value1.getGenericValue();
        final Number enumValue2 = value2.getGenericValue();
        if (enumValue1 instanceof BigInteger)
            return compare((BigInteger)enumValue1, (BigInteger)enumValue2);
        else
            return compare(enumValue1.longValue(), enumValue2.longValue());
    }

    private static int compareNulls(Object value1, Object value2)
    {
        if (value1 == null)
            return (value2 == null) ? 0 : -1;

        return 1;
    }
}