@Override
  protected boolean matchesSafely(final Metadata metadata, final Description description) {
    if (idIsSetAndDoesNotMatchWith(metadata)) {
      description.appendText("Metadata with id = " + metadata.id());
      return false;
    }

    if (nameIsSetAndDoesNotMatchWith(metadata)) {
      description.appendText("Metadata with name = " + metadata.name());
      return false;
    }

    if (causationIdsDoNotMatchWith(metadata)) {
      description.appendText("Metadata with causationIds = " + metadata.causation());
      return false;
    }

    if (userIdIsSetAndDoesNotMatchWith(metadata)) {
      description.appendText("Metadata with userId = " + metadata.userId().orElse(NOT_SET));
      return false;
    }

    if (sessionIdIsSetAndDoesNotMatchWith(metadata)) {
      description.appendText("Metadata with sessionId = " + metadata.sessionId().orElse(NOT_SET));
      return false;
    }

    if (streamIdIsSetAndDoesNotMatchWith(metadata)) {
      description.appendText(
          "Metadata with streamId = " + metadata.streamId().map(UUID::toString).orElse(NOT_SET));
      return false;
    }

    if (versionIsSetAndDoesNotMatchWith(metadata)) {
      description.appendText(
          "Metadata with version = " + metadata.version().map(String::valueOf).orElse(NOT_SET));
      return false;
    }

    if (clientCorrelationIdIsSetAndDoesNotMatchWith(metadata)) {
      description.appendText(
          "Metadata with clientCorrelationId = "
              + metadata.clientCorrelationId().map(String::valueOf).orElse(NOT_SET));
      return false;
    }

    if (jsonMatcherIsSetAndDoesNotMatchWith(metadata)) {
      description.appendText("Metadata ");
      jsonMatcher.ifPresent(
          matcher -> matcher.describeMismatch(metadata.asJsonObject().toString(), description));
      return false;
    }

    return true;
  }
  /**
   * Directly match a given metadata instance
   *
   * @param metadata the metadata to match
   * @return the matcher instance
   */
  public JsonEnvelopeMetadataMatcher of(final Metadata metadata) {
    id = Optional.of(metadata.id());
    name = Optional.of(metadata.name());
    userId = metadata.userId();
    sessionId = metadata.sessionId();
    streamId = metadata.streamId();
    version = metadata.version();

    final List<UUID> causation = metadata.causation();
    causationIds = Optional.of(causation.toArray(new UUID[causation.size()]));

    return this;
  }
  /**
   * Does a match of a given metadata instance as though the JsonEnvelope was enveloped using the
   * given metadata. The id and name are ignored, and the id is added to the causation id list.
   *
   * @param metadata the metadata to match
   * @return the matcher instance
   */
  public JsonEnvelopeMetadataMatcher envelopedWith(final Metadata metadata) {
    id = Optional.empty();
    name = Optional.empty();
    userId = metadata.userId();
    sessionId = metadata.sessionId();
    streamId = metadata.streamId();
    version = metadata.version();

    final List<UUID> causation = metadata.causation();
    final UUID[] uuids = causation.toArray(new UUID[causation.size() + 1]);
    uuids[uuids.length - 1] = metadata.id();
    causationIds = Optional.of(uuids);

    return this;
  }
 private boolean userIdIsSetAndDoesNotMatchWith(final Metadata metadata) {
   return userId.isPresent() && !userId.equals(metadata.userId());
 }