content: /*
   * Copyright (C) 2015 The Android Open Source Project
   *
   * 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.
   */
  
  package androidx.test.espresso.intent.matcher;
  
  import static androidx.test.espresso.intent.Checks.checkNotNull;
  import static org.hamcrest.Matchers.any;
  import static org.hamcrest.Matchers.anyOf;
  import static org.hamcrest.Matchers.anything;
  import static org.hamcrest.Matchers.is;
  import static org.hamcrest.Matchers.nullValue;
  
  import android.os.Bundle;
  import androidx.annotation.NonNull;
  import com.google.errorprone.annotations.CheckReturnValue;
  import org.hamcrest.Description;
  import org.hamcrest.Matcher;
  import org.hamcrest.TypeSafeMatcher;
  
  /** A collection of hamcrest matchers to match {@link Bundle} objects. */
  @CheckReturnValue
  public final class BundleMatchers {
    private BundleMatchers() {}
  
    private static class BundleMatcher extends TypeSafeMatcher<Bundle> {
      private final Matcher<String> keyMatcher;
      private final Matcher<?> valueMatcher;
      /**
       * Whether the matcher should check for presence or absence of the key and value.
       *
       * <p>If true, the matcher will match bundles that have a matching key-value pair. If false, the
       * matcher will match bundles that do not have a matching key-value pair (including empty
       * bundles).
       */
      private final boolean shouldBePresent;
  
      BundleMatcher(Matcher<String> keyMatcher, Matcher<?> valueMatcher, boolean shouldBePresent) {
        super(Bundle.class);
        this.keyMatcher = checkNotNull(keyMatcher);
        this.valueMatcher = checkNotNull(valueMatcher);
        this.shouldBePresent = shouldBePresent;
      }
  
      @Override
      public boolean matchesSafely(Bundle bundle) {
        for (String key : bundle.keySet()) {
          if (keyMatcher.matches(key) && valueMatcher.matches(bundle.get(key))) {
            return shouldBePresent;
          }
        }
        return !shouldBePresent;
      }
  
      @Override
      public void describeTo(Description description) {
        description.appendText("has bundle ");
        description.appendText(shouldBePresent ? "with" : "without");
        description.appendText(": key: ");
        description.appendDescriptionOf(keyMatcher);
        description.appendText(" value: ");
        description.appendDescriptionOf(valueMatcher);
      }
    }
  
    private static final class EmptyBundleMatcher extends TypeSafeMatcher<Bundle> {
  
      @Override
      protected boolean matchesSafely(Bundle bundle) {
        return bundle.isEmpty();
      }
  
      @Override
      public void describeTo(Description description) {
        description.appendText("is empty bundle");
      }
    }
  
    public static Matcher<Bundle> isEmpty() {
      return new EmptyBundleMatcher();
    }
  
    public static Matcher<Bundle> isEmptyOrNull() {
      return anyOf(nullValue(), isEmpty());
    }
  
    public static <T> Matcher<Bundle> hasEntry(String key, T value) {
      return hasEntry(is(key), is(value));
    }
  
    public static Matcher<Bundle> hasEntry(String key, Matcher<?> valueMatcher) {
      return hasEntry(is(key), valueMatcher);
    }
  
    public static Matcher<Bundle> hasEntry(Matcher<String> keyMatcher, Matcher<?> valueMatcher) {
      return new BundleMatcher(keyMatcher, valueMatcher, /* shouldBePresent= */ true);
    }
  
    public static Matcher<Bundle> hasKey(String key) {
      return hasKey(is(key));
    }
  
    public static Matcher<Bundle> hasKey(Matcher<String> keyMatcher) {
      return new BundleMatcher(keyMatcher, anything(), /* shouldBePresent= */ true);
    }
  
    @NonNull
    public static Matcher<Bundle> doesNotHaveKey(@NonNull String key) {
      return doesNotHaveKey(is(key));
    }
  
    @NonNull
    public static Matcher<Bundle> doesNotHaveKey(@NonNull Matcher<String> keyMatcher) {
      return new BundleMatcher(keyMatcher, anything(), /* shouldBePresent= */ false);
    }
  
    public static <T> Matcher<Bundle> hasValue(T value) {
      return hasValue(is(value));
    }
  
    public static Matcher<Bundle> hasValue(Matcher<?> valueMatcher) {
      return new BundleMatcher(any(String.class), valueMatcher, /* shouldBePresent= */ true);
    }
  }
  