All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.fitbur.assertj.internal.Objects Maven / Gradle / Ivy

There is a newer version: 1.0.0
Show newest version
/**
 * Licensed 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.
 *
 * Copyright 2012-2016 the original author or authors.
 */
package com.fitbur.assertj.internal;

import static java.lang.String.format;
import static java.util.Arrays.asList;
import static com.fitbur.assertj.error.ShouldBeEqual.shouldBeEqual;
import static com.fitbur.assertj.error.ShouldBeEqualByComparingFieldByFieldRecursively.shouldBeEqualByComparingFieldByFieldRecursive;
import static com.fitbur.assertj.error.ShouldBeEqualByComparingOnlyGivenFields.shouldBeEqualComparingOnlyGivenFields;
import static com.fitbur.assertj.error.ShouldBeEqualToIgnoringFields.shouldBeEqualToIgnoringGivenFields;
import static com.fitbur.assertj.error.ShouldBeExactlyInstanceOf.shouldBeExactlyInstance;
import static com.fitbur.assertj.error.ShouldBeIn.shouldBeIn;
import static com.fitbur.assertj.error.ShouldBeInstance.shouldBeInstance;
import static com.fitbur.assertj.error.ShouldBeInstanceOfAny.shouldBeInstanceOfAny;
import static com.fitbur.assertj.error.ShouldBeOfClassIn.shouldBeOfClassIn;
import static com.fitbur.assertj.error.ShouldBeSame.shouldBeSame;
import static com.fitbur.assertj.error.ShouldHavePropertyOrField.shouldHavePropertyOrField;
import static com.fitbur.assertj.error.ShouldHavePropertyOrFieldWithValue.shouldHavePropertyOrFieldWithValue;
import static com.fitbur.assertj.error.ShouldHaveSameClass.shouldHaveSameClass;
import static com.fitbur.assertj.error.ShouldHaveToString.shouldHaveToString;
import static com.fitbur.assertj.error.ShouldNotBeEqual.shouldNotBeEqual;
import static com.fitbur.assertj.error.ShouldNotBeExactlyInstanceOf.shouldNotBeExactlyInstance;
import static com.fitbur.assertj.error.ShouldNotBeIn.shouldNotBeIn;
import static com.fitbur.assertj.error.ShouldNotBeInstance.shouldNotBeInstance;
import static com.fitbur.assertj.error.ShouldNotBeInstanceOfAny.shouldNotBeInstanceOfAny;
import static com.fitbur.assertj.error.ShouldNotBeNull.shouldNotBeNull;
import static com.fitbur.assertj.error.ShouldNotBeOfClassIn.shouldNotBeOfClassIn;
import static com.fitbur.assertj.error.ShouldNotBeSame.shouldNotBeSame;
import static com.fitbur.assertj.error.ShouldNotHaveSameClass.shouldNotHaveSameClass;
import static com.fitbur.assertj.internal.CommonValidations.checkTypeIsNotNull;
import static com.fitbur.assertj.internal.DeepDifference.determineDifferences;
import static com.fitbur.assertj.util.Lists.newArrayList;
import static com.fitbur.assertj.util.Preconditions.checkNotNull;
import static com.fitbur.assertj.util.Sets.newLinkedHashSet;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.fitbur.assertj.api.AssertionInfo;
import com.fitbur.assertj.internal.DeepDifference.Difference;
import com.fitbur.assertj.util.VisibleForTesting;
import com.fitbur.assertj.util.introspection.FieldSupport;
import com.fitbur.assertj.util.introspection.IntrospectionError;
import com.fitbur.assertj.util.introspection.PropertyOrFieldSupport;
import com.fitbur.assertj.util.introspection.PropertySupport;

/**
 * Reusable assertions for {@code Object}s.
 * 
 * @author Yvonne Wang
 * @author Alex Ruiz
 * @author Nicolas François
 * @author Mikhail Mazursky
 */
public class Objects {

  private static final Objects INSTANCE = new Objects();
  @VisibleForTesting
  final PropertySupport propertySupport = PropertySupport.instance();
  private final ComparisonStrategy comparisonStrategy;
  @VisibleForTesting
  Failures failures = Failures.instance();
  private final FieldSupport fieldSupport = FieldSupport.comparison();

  /**
   * Returns the singleton instance of this class based on {@link StandardComparisonStrategy}.
   * 
   * @return the singleton instance of this class based on {@link StandardComparisonStrategy}.
   */
  public static Objects instance() {
    return INSTANCE;
  }

  @VisibleForTesting
  Objects() {
    this(StandardComparisonStrategy.instance());
  }

  public Objects(ComparisonStrategy comparisonStrategy) {
    this.comparisonStrategy = comparisonStrategy;
  }

  @VisibleForTesting
  public Comparator getComparator() {
    return comparisonStrategy instanceof ComparatorBasedComparisonStrategy
        ? ((ComparatorBasedComparisonStrategy) comparisonStrategy).getComparator() : null;
  }

  @VisibleForTesting
  public ComparisonStrategy getComparisonStrategy() {
    return comparisonStrategy;
  }

  /**
   * Verifies that the given object is an instance of the given type.
   *
   * @param info contains information about the assertion.
   * @param actual the given object.
   * @param type the type to check the given object against.
   * @throws NullPointerException if the given type is {@code null}.
   * @throws AssertionError if the given object is {@code null}.
   * @throws AssertionError if the given object is not an instance of the given type.
   */
  public void assertIsInstanceOf(AssertionInfo info, Object actual, Class type) {
    if (!isInstanceOfClass(actual, type, info)) throw failures.failure(info, shouldBeInstance(actual, type));
  }

  /**
   * Verifies that the given object is an instance of any of the given types.
   *
   * @param info contains information about the assertion.
   * @param actual the given object.
   * @param types the types to check the given object against.
   * @throws NullPointerException if the given array is {@code null}.
   * @throws IllegalArgumentException if the given array is empty.
   * @throws NullPointerException if the given array has {@code null} elements.
   * @throws AssertionError if the given object is {@code null}.
   * @throws AssertionError if the given object is not an instance of any of the given types.
   */
  public void assertIsInstanceOfAny(AssertionInfo info, Object actual, Class[] types) {
    if (objectIsInstanceOfOneOfGivenClasses(actual, types, info)) return;
    throw failures.failure(info, shouldBeInstanceOfAny(actual, types));
  }

  private boolean objectIsInstanceOfOneOfGivenClasses(Object actual, Class[] types, AssertionInfo info) {
    checkIsNotNullAndIsNotEmpty(types);
    assertNotNull(info, actual);
    for (Class type : types) {
      String format = "The given array of types:<%s> should not have null elements";
      checkNotNull(type, format(format, info.representation().toStringOf(types)));
      if (type.isInstance(actual)) {
        return true;
      }
    }
    return false;
  }

  /**
   * Verifies that the given object is not an instance of the given type.
   *
   * @param info contains information about the assertion.
   * @param actual the given object.
   * @param type the type to check the given object against.
   * @throws NullPointerException if the given type is {@code null}.
   * @throws AssertionError if the given object is {@code null}.
   * @throws AssertionError if the given object is an instance of the given type.
   */
  public void assertIsNotInstanceOf(AssertionInfo info, Object actual, Class type) {
    if (isInstanceOfClass(actual, type, info)) throw failures.failure(info, shouldNotBeInstance(actual, type));
  }

  private boolean isInstanceOfClass(Object actual, Class clazz, AssertionInfo info) {
    assertNotNull(info, actual);
    checkTypeIsNotNull(clazz);
    return clazz.isInstance(actual);
  }

  /**
   * Verifies that the given object is not an instance of any of the given types.
   *
   * @param info contains information about the assertion.
   * @param actual the given object.
   * @param types the types to check the given object against.
   * @throws NullPointerException if the given array is {@code null}.
   * @throws IllegalArgumentException if the given array is empty.
   * @throws NullPointerException if the given array has {@code null} elements.
   * @throws AssertionError if the given object is {@code null}.
   * @throws AssertionError if the given object is an instance of any of the given types.
   */
  public void assertIsNotInstanceOfAny(AssertionInfo info, Object actual, Class[] types) {
    if (!objectIsInstanceOfOneOfGivenClasses(actual, types, info)) return;
    throw failures.failure(info, shouldNotBeInstanceOfAny(actual, types));
  }

  /**
   * Verifies that the actual value has the same class as the given object.
   *
   * @param info contains information about the assertion.
   * @param actual the given object.
   * @throws AssertionError if the actual has not the same type has the given object.
   * @throws NullPointerException if the actual value is null.
   * @throws NullPointerException if the given object is null.
   */
  public void assertHasSameClassAs(AssertionInfo info, Object actual, Object other) {
    if (!haveSameClass(actual, other, info)) throw failures.failure(info, shouldHaveSameClass(actual, other));
  }

  private boolean haveSameClass(Object actual, Object other, AssertionInfo info) {
    assertNotNull(info, actual);
    checkNotNull(other, "The given object should not be null");
    Class actualClass = actual.getClass();
    Class otherClass = other.getClass();
    return actualClass.equals(otherClass);
  }

  /**
   * Verifies that the actual value does not have the same class as the given object.
   *
   * @param info contains information about the assertion.
   * @param actual the given object.
   * @param other the object to check type against.
   * @throws AssertionError if the actual has the same type has the given object.
   * @throws NullPointerException if the actual value is null.
   * @throws NullPointerException if the given object is null.
   */
  public void assertDoesNotHaveSameClassAs(AssertionInfo info, Object actual, Object other) {
    if (haveSameClass(actual, other, info)) throw failures.failure(info, shouldNotHaveSameClass(actual, other));
  }

  /**
   * Verifies that the actual value is exactly an instance of given type.
   *
   * @param info contains information about the assertion.
   * @param actual the given object.
   * @param type the type to check the actual value against.
   * @throws AssertionError if the actual is not exactly an instance of given type.
   * @throws NullPointerException if the actual value is null.
   * @throws NullPointerException if the given object is null.
   */
  public void assertIsExactlyInstanceOf(AssertionInfo info, Object actual, Class type) {
    if (!actualIsExactlyInstanceOfType(actual, type, info))
      throw failures.failure(info, shouldBeExactlyInstance(actual, type));
  }

  private boolean actualIsExactlyInstanceOfType(Object actual, Class expectedType, AssertionInfo info) {
    assertNotNull(info, actual);
    checkTypeIsNotNull(expectedType);
    return expectedType.equals(actual.getClass());
  }

  /**
   * Verifies that the actual value is not exactly an instance of given type.
   *
   * @param info contains information about the assertion.
   * @param actual the given object.
   * @param type the type to check the actual value against.
   * @throws AssertionError if the actual is exactly an instance of given type.
   * @throws NullPointerException if the actual value is null.
   * @throws NullPointerException if the given object is null.
   */
  public void assertIsNotExactlyInstanceOf(AssertionInfo info, Object actual, Class type) {
    if (actualIsExactlyInstanceOfType(actual, type, info))
      throw failures.failure(info, shouldNotBeExactlyInstance(actual, type));
  }

  /**
   * Verifies that the actual value type is in given types.
   *
   * @param info contains information about the assertion.
   * @param actual the given object.
   * @param types the types to check the actual value against.
   * @throws AssertionError if the actual value type is in given type.
   * @throws NullPointerException if the actual value is null.
   * @throws NullPointerException if the given types is null.
   */
  public void assertIsOfAnyClassIn(AssertionInfo info, Object actual, Class[] types) {
    boolean itemInArray = isOfOneOfGivenTypes(actual, types, info);
    if (!itemInArray) throw failures.failure(info, shouldBeOfClassIn(actual, types));
  }

  private boolean isOfOneOfGivenTypes(Object actual, Class[] types, AssertionInfo info) {
    assertNotNull(info, actual);
    checkNotNull(types, "The given types should not be null");
    return isItemInArray(actual.getClass(), types);
  }

  /**
   * Verifies that the actual value type is not in given types.
   *
   * @param info contains information about the assertion.
   * @param actual the given object.
   * @param types the types to check the actual value against.
   * @throws AssertionError if the actual value type is in given type.
   * @throws NullPointerException if the actual value is null.
   * @throws NullPointerException if the given types is null.
   */
  public void assertIsNotOfAnyClassIn(AssertionInfo info, Object actual, Class[] types) {
    boolean itemInArray = isOfOneOfGivenTypes(actual, types, info);
    if (itemInArray) throw failures.failure(info, shouldNotBeOfClassIn(actual, types));
  }

  private void checkIsNotNullAndIsNotEmpty(Class[] types) {
    checkNotNull(types, "The given array of types should not be null");
    if (types.length == 0) {
      throw new IllegalArgumentException("The given array of types should not be empty");
    }
  }

  /**
   * Asserts that two objects are equal.
   *
   * @param info contains information about the assertion.
   * @param actual the "actual" object.
   * @param expected the "expected" object.
   * @throws AssertionError if {@code actual} is not equal to {@code expected}. This method will throw a
   *           {@code org.junit.ComparisonFailure} instead if JUnit is in the classpath and the given objects are not
   *           equal.
   */
  public void assertEqual(AssertionInfo info, Object actual, Object expected) {
    if (areEqual(actual, expected)) {
      return;
    }
    throw failures.failure(info, shouldBeEqual(actual, expected, comparisonStrategy, info.representation()));
  }

  /**
   * Asserts that two objects are not equal.
   *
   * @param info contains information about the assertion.
   * @param actual the given object.
   * @param other the object to compare {@code actual} to.
   * @throws AssertionError if {@code actual} is equal to {@code other}.
   */
  public void assertNotEqual(AssertionInfo info, Object actual, Object other) {
    if (!areEqual(actual, other)) {
      return;
    }
    throw failures.failure(info, shouldNotBeEqual(actual, other, comparisonStrategy));
  }

  /**
   * Compares actual and other with standard strategy (null safe equals check).
   *
   * @param actual the object to compare to other
   * @param other the object to compare to actual
   * @return true if actual and other are equal (null safe equals check), false otherwise.
   */
  private boolean areEqual(Object actual, Object other) {
    return comparisonStrategy.areEqual(actual, other);
  }

  /**
   * Asserts that the given object is {@code null}.
   *
   * @param info contains information about the assertion.
   * @param actual the given object.
   * @throws AssertionError if the given object is not {@code null}.
   */
  public void assertNull(AssertionInfo info, Object actual) {
    if (actual == null) {
      return;
    }
    throw failures.failure(info, shouldBeEqual(actual, null, comparisonStrategy, info.representation()));
  }

  /**
   * Asserts that the given object is not {@code null}.
   *
   * @param info contains information about the assertion.
   * @param actual the given object.
   * @throws AssertionError if the given object is {@code null}.
   */
  public void assertNotNull(AssertionInfo info, Object actual) {
    if (actual != null) {
      return;
    }
    throw failures.failure(info, shouldNotBeNull());
  }

  /**
   * Asserts that two objects refer to the same object.
   *
   * @param info contains information about the assertion.
   * @param actual the given object.
   * @param expected the expected object.
   * @throws AssertionError if the given objects do not refer to the same object.
   */
  public void assertSame(AssertionInfo info, Object actual, Object expected) {
    if (actual == expected) {
      return;
    }
    throw failures.failure(info, shouldBeSame(actual, expected));
  }

  /**
   * Asserts that two objects do not refer to the same object.
   *
   * @param info contains information about the assertion.
   * @param actual the given object.
   * @param other the object to compare {@code actual} to.
   * @throws AssertionError if the given objects refer to the same object.
   */
  public void assertNotSame(AssertionInfo info, Object actual, Object other) {
    if (actual != other) {
      return;
    }
    throw failures.failure(info, shouldNotBeSame(actual));
  }

  public void assertHasToString(AssertionInfo info, Object actual, String expectedToString) {
    assertNotNull(info, actual);
    if (!actual.toString().equals(expectedToString))
      throw failures.failure(info, shouldHaveToString(actual, expectedToString));
  }

  /**
   * Asserts that the given object is present in the given array.
   *
   * @param info contains information about the assertion.
   * @param actual the given object.
   * @param values the given array.
   * @throws NullPointerException if the given array is {@code null}.
   * @throws IllegalArgumentException if the given array is empty.
   * @throws AssertionError if the given object is not present in the given array.
   */
  public void assertIsIn(AssertionInfo info, Object actual, Object[] values) {
    checkIsNotNullAndNotEmpty(values);
    assertIsIn(info, actual, asList(values));
  }

  /**
   * Asserts that the given object is not present in the given array.
   *
   * @param info contains information about the assertion.
   * @param actual the given object.
   * @param values the given array.
   * @throws NullPointerException if the given array is {@code null}.
   * @throws IllegalArgumentException if the given array is empty.
   * @throws AssertionError if the given object is present in the given array.
   */
  public void assertIsNotIn(AssertionInfo info, Object actual, Object[] values) {
    checkIsNotNullAndNotEmpty(values);
    assertIsNotIn(info, actual, asList(values));
  }

  private void checkIsNotNullAndNotEmpty(Object[] values) {
    checkNotNull(values, "The given array should not be null");
    if (values.length == 0) {
      throw new IllegalArgumentException("The given array should not be empty");
    }
  }

  /**
   * Returns true if given item is in given array, false otherwise.
   *
   * @param item the object to look for in arrayOfValues
   * @param arrayOfValues the array of values
   * @return true if given item is in given array, false otherwise.
   */
  private boolean isItemInArray(Object item, Object[] arrayOfValues) {
    for (Object value : arrayOfValues) {
      if (areEqual(value, item)) return true;
    }
    return false;
  }

  /**
   * Asserts that the given object is present in the given collection.
   *
   * @param info contains information about the assertion.
   * @param actual the given object.
   * @param values the given iterable.
   * @throws NullPointerException if the given collection is {@code null}.
   * @throws IllegalArgumentException if the given collection is empty.
   * @throws AssertionError if the given object is not present in the given collection.
   */
  public void assertIsIn(AssertionInfo info, Object actual, Iterable values) {
    checkIsNotNullAndNotEmpty(values);
    if (!isActualIn(actual, values)) throw failures.failure(info, shouldBeIn(actual, values, comparisonStrategy));
  }

  /**
   * Asserts that the given object is not present in the given collection.
   *
   * @param info contains information about the assertion.
   * @param actual the given object.
   * @param values the given collection.
   * @throws NullPointerException if the given iterable is {@code null}.
   * @throws IllegalArgumentException if the given collection is empty.
   * @throws AssertionError if the given object is present in the given collection.
   */
  public void assertIsNotIn(AssertionInfo info, Object actual, Iterable values) {
    checkIsNotNullAndNotEmpty(values);
    if (isActualIn(actual, values)) throw failures.failure(info, shouldNotBeIn(actual, values, comparisonStrategy));
  }

  private void checkIsNotNullAndNotEmpty(Iterable values) {
    checkNotNull(values, "The given iterable should not be null");
    if (!values.iterator().hasNext()) {
      throw new IllegalArgumentException("The given iterable should not be empty");
    }
  }

  private boolean isActualIn(Object actual, Iterable values) {
    for (Object value : values) {
      if (areEqual(value, actual)) {
        return true;
      }
    }
    return false;
  }

  /**
   * Assert that the given object is lenient equals by ignoring null fields value on other object (including inherited
   * fields).
   *
   * @param info contains information about the assertion.
   * @param actual the given object.
   * @param other the object to compare {@code actual} to.
   * @throws NullPointerException if the actual type is {@code null}.
   * @throws NullPointerException if the other type is {@code null}.
   * @throws AssertionError if the actual and the given object are not lenient equals.
   * @throws AssertionError if the other object is not an instance of the actual type.
   */
  public  void assertIsEqualToIgnoringNullFields(AssertionInfo info, A actual, A other,
                                                    Map> comparatorByPropertyOrField,
                                                    Map, Comparator> comparatorByType) {
    assertNotNull(info, actual);
    List fieldsNames = new LinkedList<>();
    List rejectedValues = new LinkedList<>();
    List expectedValues = new LinkedList<>();
    List nullFields = new LinkedList<>();
    for (Field field : getDeclaredFieldsIncludingInherited(actual.getClass())) {
      if (!canReadFieldValue(field, actual)) continue;
      String fieldName = field.getName();
      Object otherFieldValue = getPropertyOrFieldValue(other, fieldName);
      if (otherFieldValue == null) {
        nullFields.add(fieldName);
      } else {
        Object actualFieldValue = getPropertyOrFieldValue(actual, fieldName);
        if (!propertyOrFieldValuesAreEqual(actualFieldValue, otherFieldValue, fieldName,
                                           comparatorByPropertyOrField, comparatorByType)) {
          fieldsNames.add(fieldName);
          rejectedValues.add(actualFieldValue);
          expectedValues.add(otherFieldValue);
        }
      }
    }
    if (!fieldsNames.isEmpty())
      throw failures.failure(info, shouldBeEqualToIgnoringGivenFields(actual, fieldsNames,
                                                                      rejectedValues, expectedValues, nullFields));
  }

  /**
   * Assert that the given object is lenient equals to other object by comparing given fields value only.
   *
   * @param info contains information about the assertion.
   * @param actual the given object.
   * @param other the object to compare {@code actual} to.
   * @param fields accepted fields
   * @throws NullPointerException if the other type is {@code null}.
   * @throws AssertionError if actual is {@code null}.
   * @throws AssertionError if the actual and the given object are not lenient equals.
   * @throws AssertionError if the other object is not an instance of the actual type.
   * @throws IntrospectionError if a field does not exist in actual.
   */
  public  void assertIsEqualToComparingOnlyGivenFields(AssertionInfo info, A actual, A other,
                                                          Map> comparatorByPropertyOrField,
                                                          Map, Comparator> comparatorByType,
                                                          String... fields) {
    assertNotNull(info, actual);
    ByFieldsComparison byFieldsComparison = isEqualToComparingOnlyGivenFields(actual, other,
                                                                              comparatorByPropertyOrField,
                                                                              comparatorByType,
                                                                              fields);
    if (byFieldsComparison.isFieldsNamesNotEmpty())
      throw failures.failure(info, shouldBeEqualComparingOnlyGivenFields(actual, byFieldsComparison.fieldsNames,
                                                                         byFieldsComparison.rejectedValues,
                                                                         byFieldsComparison.expectedValues,
                                                                         newArrayList(fields)));
  }

  private  ByFieldsComparison isEqualToComparingOnlyGivenFields(A actual, A other,
                                                                   Map> comparatorByPropertyOrField,
                                                                   Map, Comparator> comparatorByType,
                                                                   String[] fields) {
    List rejectedFieldsNames = new LinkedList<>();
    List expectedValues = new LinkedList<>();
    List rejectedValues = new LinkedList<>();
    for (String fieldName : fields) {
      Object actualFieldValue = getPropertyOrFieldValue(actual, fieldName);
      Object otherFieldValue = getPropertyOrFieldValue(other, fieldName);
      if (!propertyOrFieldValuesAreEqual(actualFieldValue, otherFieldValue, fieldName, comparatorByPropertyOrField,
                                         comparatorByType)) {
        rejectedFieldsNames.add(fieldName);
        expectedValues.add(otherFieldValue);
        rejectedValues.add(actualFieldValue);
      }
    }
    return new ByFieldsComparison(rejectedFieldsNames, expectedValues, rejectedValues);
  }

  /**
   * Assert that the given object is lenient equals to the other by comparing all fields (including inherited fields)
   * unless given ignored ones.
   *
   * @param info contains information about the assertion.
   * @param actual the given object.
   * @param other the object to compare {@code actual} to.
   * @param fields the fields to ignore in comparison
   * @throws NullPointerException if the other type is {@code null}.
   * @throws AssertionError if actual is {@code null}.
   * @throws AssertionError if the actual and the given object are not lenient equals.
   * @throws AssertionError if the other object is not an instance of the actual type.
   */
  public  void assertIsEqualToIgnoringGivenFields(AssertionInfo info, A actual, A other,
                                                     Map> comparatorByPropertyOrField,
                                                     Map, Comparator> comparatorByType, String... fields) {
    assertNotNull(info, actual);
    ByFieldsComparison byFieldsComparison = isEqualToIgnoringGivenFields(actual, other, comparatorByPropertyOrField,
                                                                         comparatorByType, fields);
    if (byFieldsComparison.isFieldsNamesNotEmpty())
      throw failures.failure(info, shouldBeEqualToIgnoringGivenFields(actual, byFieldsComparison.fieldsNames,
                                                                      byFieldsComparison.rejectedValues,
                                                                      byFieldsComparison.expectedValues,
                                                                      newArrayList(fields)));
  }

  private  ByFieldsComparison isEqualToIgnoringGivenFields(A actual, A other,
                                                              Map> comparatorByPropertyOrField,
                                                              Map, Comparator> comparatorByType,
                                                              String[] givenIgnoredFields) {
    Set declaredFieldsIncludingInherited = getDeclaredFieldsIncludingInherited(actual.getClass());
    List fieldsNames = new LinkedList<>();
    List expectedValues = new LinkedList<>();
    List rejectedValues = new LinkedList<>();
    Set ignoredFields = newLinkedHashSet(givenIgnoredFields);
    for (Field field : declaredFieldsIncludingInherited) {
      // ignore private field if user has decided not to use them in comparison
      String fieldName = field.getName();
      if (ignoredFields.contains(fieldName) || !canReadFieldValue(field, actual)) {
        continue;
      }
      Object actualFieldValue = getPropertyOrFieldValue(actual, fieldName);
      Object otherFieldValue = getPropertyOrFieldValue(other, fieldName);

      if (!propertyOrFieldValuesAreEqual(actualFieldValue, otherFieldValue, fieldName,
                                         comparatorByPropertyOrField, comparatorByType)) {
        fieldsNames.add(fieldName);
        rejectedValues.add(actualFieldValue);
        expectedValues.add(otherFieldValue);
      }
    }
    return new ByFieldsComparison(fieldsNames, expectedValues, rejectedValues);
  }

  @SuppressWarnings({ "unchecked", "rawtypes" })
  static boolean propertyOrFieldValuesAreEqual(Object actualFieldValue, Object otherFieldValue, String fieldName,
                                                Map> comparatorByPropertyOrField,
                                                Map, Comparator> comparatorByType) {
    if (actualFieldValue != null && otherFieldValue != null
        && actualFieldValue.getClass() == otherFieldValue.getClass()) {
      Comparator fieldComparator = comparatorByPropertyOrField.containsKey(fieldName)
          ? comparatorByPropertyOrField.get(fieldName) : comparatorByType.get(actualFieldValue.getClass());
      if (fieldComparator != null) return fieldComparator.compare(actualFieldValue, otherFieldValue) == 0;
    }
    return com.fitbur.assertj.util.Objects.areEqual(actualFieldValue, otherFieldValue);
  }

  private  boolean canReadFieldValue(Field field, A actual) {
    return fieldSupport.isAllowedToRead(field) || propertySupport.publicGetterExistsFor(field.getName(), actual);
  }

  /**
   * Assert that the given object is "deeply" equals to other by comparing all fields recursively.
   *
   * @param info contains information about the assertion.
   * @param actual the given object.
   * @param other the object to compare {@code actual} to.
   * @throws AssertionError if actual is {@code null}.
   * @throws AssertionError if the actual and the given object are not "deeply" equal.
   */
  public  void assertIsEqualToComparingFieldByFieldRecursively(AssertionInfo info, A actual, A other,
                                                                  Map> comparatorByPropertyOrField,
                                                                  Map, Comparator> comparatorByType) {
    assertNotNull(info, actual);
    List differences = determineDifferences(actual, other, comparatorByPropertyOrField, comparatorByType);
    if (!differences.isEmpty()) {
      throw failures.failure(info, shouldBeEqualByComparingFieldByFieldRecursive(actual, other, differences));
    }
  }

  /**
   * Get property value first and in case of error try field value.
   * 

* This method supports nested field/property (e.g. "address.street.number"). * * @param a the object to get field value from * @param fieldName Field name to read, can be nested * @return (nested) field value or property value if field was not accessible. * @throws IntrospectionError is field value can't get retrieved. */ private Object getPropertyOrFieldValue(A a, String fieldName) { return PropertyOrFieldSupport.COMPARISON.getValueOf(fieldName, a); } /** * Returns the declared fields of given class and its superclasses stopping at superclass in java.lang * package whose fields are not included. * * @param clazz the class we want the declared fields. * @return the declared fields of given class and its superclasses. */ public static Set getDeclaredFieldsIncludingInherited(Class clazz) { checkNotNull(clazz, "expecting Class parameter not to be null"); Set declaredFields = getDeclaredFieldsIgnoringSynthetic(clazz); // get fields declared in superclass Class superclazz = clazz.getSuperclass(); while (superclazz != null && !superclazz.getName().startsWith("java.lang")) { declaredFields.addAll(getDeclaredFieldsIgnoringSynthetic(superclazz)); superclazz = superclazz.getSuperclass(); } return declaredFields; } /** * Returns the declared fields of a given class excluding any synthetic fields. Synthetic fields are fields that are * generated by the compiler for access purposes, or by instrumentation tools e.g. JaCoCo adds in a $jacocoData field * and therefore should be ignored when comparing fields. * * @param clazz the class we want the declared fields. * @return the declared fields of given class excluding any synthetic fields. */ private static Set getDeclaredFieldsIgnoringSynthetic(Class clazz) { final Set fields = newLinkedHashSet(clazz.getDeclaredFields()); for (Iterator iterator = fields.iterator(); iterator.hasNext();) { if (iterator.next().isSynthetic()) iterator.remove(); } return fields; } public boolean areEqualToIgnoringGivenFields(Object actual, Object other, Map> comparatorByPropertyOrField, Map, Comparator> comparatorByType, String... fields) { return isEqualToIgnoringGivenFields(actual, other, comparatorByPropertyOrField, comparatorByType, fields).isFieldsNamesEmpty(); } public boolean areEqualToComparingOnlyGivenFields(Object actual, Object other, Map> comparatorByPropertyOrField, Map, Comparator> comparatorByType, String... fields) { return isEqualToComparingOnlyGivenFields(actual, other, comparatorByPropertyOrField, comparatorByType, fields).isFieldsNamesEmpty(); } public void assertHasFieldOrProperty(AssertionInfo info, A actual, String name) { assertNotNull(info, actual); try { extractPropertyOrField(actual, name); } catch (IntrospectionError error) { throw failures.failure(info, shouldHavePropertyOrField(actual, name)); } } public void assertHasFieldOrPropertyWithValue(AssertionInfo info, A actual, String name, Object expectedValue) { assertHasFieldOrProperty(info, actual, name); Object value = extractPropertyOrField(actual, name); if (!com.fitbur.assertj.util.Objects.areEqual(value, expectedValue)) throw failures.failure(info, shouldHavePropertyOrFieldWithValue(actual, name, expectedValue, value)); } private Object extractPropertyOrField(A actual, String name) { return PropertyOrFieldSupport.EXTRACTION.getValueOf(name, actual); } public static class ByFieldsComparison { private final List fieldsNames; private final List expectedValues; private final List rejectedValues; public ByFieldsComparison(final List fieldsNames, final List expectedValues, final List rejectedValues) { this.fieldsNames = fieldsNames; this.expectedValues = expectedValues; this.rejectedValues = rejectedValues; } public ByFieldsComparison() { this(new ArrayList(), new ArrayList<>(), new ArrayList<>()); } public boolean isFieldsNamesEmpty() { return fieldsNames.isEmpty(); } public boolean isFieldsNamesNotEmpty() { return !isFieldsNamesEmpty(); } } }