// Path: util/src/main/java/com/spotify/hamcrest/util/DescriptionUtils.java
// public final class DescriptionUtils {
// 
//   private static final Splitter LINE_SPLITTER = Splitter.on('\n');
//   private static final Joiner INDENTED_LINE_JOINER = Joiner.on("\n  ");
// 
//   private DescriptionUtils() {
//     throw new IllegalAccessError("This class may not be instantiated.");
//   }
// 
//   public static void indentDescription(Description description, Description innerDescription) {
//     final Iterable<String> lines = LINE_SPLITTER.split(innerDescription.toString().trim());
//     final String indentedLines = INDENTED_LINE_JOINER.join(lines);
//     description.appendText(indentedLines).appendText("\n");
//   }
// 
//   /**
//    * Describes a nested mismatch, useful for nested types like Objects or Maps
//    *
//    * <p>This will print <b>all</b> mismatches occurring in a mismatch list, and properly
//    * handle ellipsis (...). Order will also be maintained based on the order of the allKeys
//    * set. To maintain input order, consider using {@link java.util.LinkedHashSet}
//    * or {@link java.util.LinkedHashMap}
//    *
//    * <p>This will also handle proper indentation in the case of nesting.
//    * Description will contain output that looks like,
//    * <pre>
//    *   {@code
//    * {
//    *   ...
//    *   myKey: expected 1 but was 2
//    *   myOtherKey: expected "hello" but was "world"
//    *   ...
//    *   nestingKey: {
//    *     nestedKey: expected null
//    *   }
//    * }
//    *   }
//    * </pre>
//    *
//    * @param allKeys             {@link Set} of all keys expecting to match
//    * @param mismatchDescription The {@link Description} to write the output to
//    * @param mismatchedKeys      A {@link Map} of all keys mismatched. The value is a
//    *                            {@link Consumer} which will write the describe the mismatch for that
//    *                            key
//    * @param describeKey         A {@link BiConsumer} used to describe the key
//    */
//   public static void describeNestedMismatches(
//       Set<String> allKeys,
//       Description mismatchDescription,
//       Map<String, Consumer<Description>> mismatchedKeys,
//       BiConsumer<String, Description> describeKey) {
//     checkArgument(!mismatchedKeys.isEmpty(), "mismatchKeys must not be empty");
//     String previousMismatchKey = null;
//     String previousKey = null;
// 
//     mismatchDescription.appendText("{\n");
// 
//     for (String key : allKeys) {
//       if (mismatchedKeys.containsKey(key)) {
//         // If this is not the first key and the previous key was not a mismatch then add ellipsis
//         if (previousKey != null && !Objects.equals(previousMismatchKey, previousKey)) {
//           mismatchDescription.appendText("  ...\n");
//         }
// 
//         describeMismatchForKey(
//             key,
//             mismatchDescription,
//             describeKey,
//             mismatchedKeys.get(key));
//         previousMismatchKey = key;
//       }
//       previousKey = key;
//     }
// 
//     // If the last element was not a mismatch then add ellipsis
//     if (!Objects.equals(previousMismatchKey, previousKey)) {
//       mismatchDescription.appendText("  ...\n");
//     }
// 
//     mismatchDescription.appendText("}");
//   }
// 
//   private static void describeMismatchForKey(
//       String key,
//       Description mismatchDescription,
//       BiConsumer<String, Description> describeKey,
//       Consumer<Description> innerAction) {
// 
//     mismatchDescription.appendText("  ");
//     describeKey.accept(String.valueOf(key), mismatchDescription);
//     mismatchDescription.appendText(": ");
// 
//     final Description innerDescription = new StringDescription();
//     innerAction.accept(innerDescription);
//     indentDescription(mismatchDescription, innerDescription);
//   }
// }


import static java.util.Arrays.stream;
import static java.util.Objects.requireNonNull;
import com.google.auto.value.AutoValue;
import com.google.common.base.CaseFormat;
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableMap;
import com.spotify.hamcrest.util.DescriptionUtils;
import java.lang.invoke.SerializedLambda;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Optional;
import java.util.function.Consumer;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.StringDescription;
import org.hamcrest.TypeSafeDiagnosingMatcher;

  /**
   * Method uses serialization trick to extract information about lambda,
   * to give understandable name in case of mismatch.
   *
   * @param lambda lambda to extract the name from
   * @return a serialized version of the lambda, containing useful information for introspection
   */
  private static SerializedLambda serializeLambda(final Object lambda) {
    requireNonNull(lambda);

    final Method writeReplace;
    try {
      writeReplace = AccessController.doPrivileged((PrivilegedExceptionAction<Method>) () -> {
        Method method = lambda.getClass().getDeclaredMethod("writeReplace");
        method.setAccessible(true);
        return method;
      });
    } catch (PrivilegedActionException e) {
      throw new IllegalStateException("Cannot serialize lambdas in unprivileged context", e);
    }

    try {
      return (SerializedLambda) writeReplace.invoke(lambda);
    } catch (ClassCastException | IllegalAccessException | InvocationTargetException e) {
      throw new IllegalArgumentException(
          "Could not serialize as a lambda (is it a lambda?): " + lambda, e);
    }
  }

// Path: util/src/main/java/com/spotify/hamcrest/util/DescriptionUtils.java
// public final class DescriptionUtils {
// 
//   private static final Splitter LINE_SPLITTER = Splitter.on('\n');
//   private static final Joiner INDENTED_LINE_JOINER = Joiner.on("\n  ");
// 
//   private DescriptionUtils() {
//     throw new IllegalAccessError("This class may not be instantiated.");
//   }
// 
//   public static void indentDescription(Description description, Description innerDescription) {
//     final Iterable<String> lines = LINE_SPLITTER.split(innerDescription.toString().trim());
//     final String indentedLines = INDENTED_LINE_JOINER.join(lines);
//     description.appendText(indentedLines).appendText("\n");
//   }
// 
//   /**
//    * Describes a nested mismatch, useful for nested types like Objects or Maps
//    *
//    * <p>This will print <b>all</b> mismatches occurring in a mismatch list, and properly
//    * handle ellipsis (...). Order will also be maintained based on the order of the allKeys
//    * set. To maintain input order, consider using {@link java.util.LinkedHashSet}
//    * or {@link java.util.LinkedHashMap}
//    *
//    * <p>This will also handle proper indentation in the case of nesting.
//    * Description will contain output that looks like,
//    * <pre>
//    *   {@code
//    * {
//    *   ...
//    *   myKey: expected 1 but was 2
//    *   myOtherKey: expected "hello" but was "world"
//    *   ...
//    *   nestingKey: {
//    *     nestedKey: expected null
//    *   }
//    * }
//    *   }
//    * </pre>
//    *
//    * @param allKeys             {@link Set} of all keys expecting to match
//    * @param mismatchDescription The {@link Description} to write the output to
//    * @param mismatchedKeys      A {@link Map} of all keys mismatched. The value is a
//    *                            {@link Consumer} which will write the describe the mismatch for that
//    *                            key
//    * @param describeKey         A {@link BiConsumer} used to describe the key
//    */
//   public static void describeNestedMismatches(
//       Set<String> allKeys,
//       Description mismatchDescription,
//       Map<String, Consumer<Description>> mismatchedKeys,
//       BiConsumer<String, Description> describeKey) {
//     checkArgument(!mismatchedKeys.isEmpty(), "mismatchKeys must not be empty");
//     String previousMismatchKey = null;
//     String previousKey = null;
// 
//     mismatchDescription.appendText("{\n");
// 
//     for (String key : allKeys) {
//       if (mismatchedKeys.containsKey(key)) {
//         // If this is not the first key and the previous key was not a mismatch then add ellipsis
//         if (previousKey != null && !Objects.equals(previousMismatchKey, previousKey)) {
//           mismatchDescription.appendText("  ...\n");
//         }
// 
//         describeMismatchForKey(
//             key,
//             mismatchDescription,
//             describeKey,
//             mismatchedKeys.get(key));
//         previousMismatchKey = key;
//       }
//       previousKey = key;
//     }
// 
//     // If the last element was not a mismatch then add ellipsis
//     if (!Objects.equals(previousMismatchKey, previousKey)) {
//       mismatchDescription.appendText("  ...\n");
//     }
// 
//     mismatchDescription.appendText("}");
//   }
// 
//   private static void describeMismatchForKey(
//       String key,
//       Description mismatchDescription,
//       BiConsumer<String, Description> describeKey,
//       Consumer<Description> innerAction) {
// 
//     mismatchDescription.appendText("  ");
//     describeKey.accept(String.valueOf(key), mismatchDescription);
//     mismatchDescription.appendText(": ");
// 
//     final Description innerDescription = new StringDescription();
//     innerAction.accept(innerDescription);
//     indentDescription(mismatchDescription, innerDescription);
//   }
// }


// Path: pojo/src/main/java/com/spotify/hamcrest/pojo/IsPojo.java
import static java.util.Arrays.stream;
import static java.util.Objects.requireNonNull;
import com.google.auto.value.AutoValue;
import com.google.common.base.CaseFormat;
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableMap;
import com.spotify.hamcrest.util.DescriptionUtils;
import java.lang.invoke.SerializedLambda;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Optional;
import java.util.function.Consumer;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.StringDescription;
import org.hamcrest.TypeSafeDiagnosingMatcher;


  /**
   * Method uses serialization trick to extract information about lambda,
   * to give understandable name in case of mismatch.
   *
   * @param lambda lambda to extract the name from
   * @return a serialized version of the lambda, containing useful information for introspection
   */
  private static SerializedLambda serializeLambda(final Object lambda) {
    requireNonNull(lambda);

    final Method writeReplace;
    try {
      writeReplace = AccessController.doPrivileged((PrivilegedExceptionAction<Method>) () -> {
        Method method = lambda.getClass().getDeclaredMethod("writeReplace");
        method.setAccessible(true);
        return method;
      });
    } catch (PrivilegedActionException e) {
      throw new IllegalStateException("Cannot serialize lambdas in unprivileged context", e);
    }

    try {
      return (SerializedLambda) writeReplace.invoke(lambda);
    } catch (ClassCastException | IllegalAccessException | InvocationTargetException e) {
      throw new IllegalArgumentException(
          "Could not serialize as a lambda (is it a lambda?): " + lambda, e);
    }
  }

  private static void ensureDirectMethodReference(final SerializedLambda serializedLambda) {