fi.jumi.core.util.EqualityMatchers.java Source code

Java tutorial

Introduction

Here is the source code for fi.jumi.core.util.EqualityMatchers.java

Source

// Copyright  2011-2013, Esko Luontola <www.orfjackal.net>
// This software is released under the Apache License 2.0.
// The license text is at http://www.apache.org/licenses/LICENSE-2.0

package fi.jumi.core.util;

import org.apache.commons.lang3.reflect.FieldUtils;
import org.hamcrest.*;

import java.lang.reflect.*;
import java.util.List;

public class EqualityMatchers {

    public static <T> Matcher<T> deepEqualTo(T expected) {
        return new TypeSafeMatcher<T>() {
            @Override
            protected boolean matchesSafely(T actual) {
                return findDifference("this", actual, expected) == null;
            }

            @Override
            public void describeTo(Description description) {
                description.appendText("deep equal to ").appendValue(expected);
            }

            @Override
            protected void describeMismatchSafely(T actual, Description mismatchDescription) {
                String path = findDifference("this", actual, expected);
                mismatchDescription.appendText("was different at ").appendValue(path).appendText(" of ")
                        .appendValue(actual);
            }
        };
    }

    private static String findDifference(String path, Object obj1, Object obj2) {
        if (obj1 == null || obj2 == null) {
            return obj1 == obj2 ? null : path;
        }
        if (obj1.getClass() != obj2.getClass()) {
            return path;
        }

        // collections have a custom equals, but we want deep equality on every collection element
        // TODO: support other collection types? comparing Sets should be order-independent
        if (obj1 instanceof List) {
            List<?> col1 = (List<?>) obj1;
            List<?> col2 = (List<?>) obj2;

            int size1 = col1.size();
            int size2 = col2.size();
            if (size1 != size2) {
                return path + ".size()";
            }

            for (int i = 0; i < Math.min(size1, size2); i++) {
                String diff = findDifference(path + ".get(" + i + ")", col1.get(i), col2.get(i));
                if (diff != null) {
                    return diff;
                }
            }
            return null;
        }

        // use custom equals method if exists
        try {
            Method equals = obj1.getClass().getMethod("equals", Object.class);
            if (equals.getDeclaringClass() != Object.class) {
                return obj1.equals(obj2) ? null : path;
            }
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        }

        // arrays
        if (obj2.getClass().isArray()) {
            int length1 = Array.getLength(obj1);
            int length2 = Array.getLength(obj2);
            if (length1 != length2) {
                return path + ".length";
            }

            for (int i = 0; i < Math.min(length1, length2); i++) {
                String diff = findDifference(path + "[" + i + "]", Array.get(obj1, i), Array.get(obj2, i));
                if (diff != null) {
                    return diff;
                }
            }
            return null;
        }

        // structural equality
        for (Class<?> cl = obj2.getClass(); cl != null; cl = cl.getSuperclass()) {
            for (Field field : cl.getDeclaredFields()) {
                try {
                    String diff = findDifference(path + "." + field.getName(),
                            FieldUtils.readField(field, obj1, true), FieldUtils.readField(field, obj2, true));
                    if (diff != null) {
                        return diff;
                    }
                } catch (IllegalAccessException e) {
                    throw new RuntimeException(e);
                }
            }
        }
        return null;
    }
}