/*
 * Copyright 2014 Ben Manes. All Rights Reserved.
 *
 * 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 com.github.benmanes.caffeine.cache.testing;

import static java.util.Objects.requireNonNull;

import java.util.Map.Entry;
import java.util.Objects;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;

import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.RemovalCause;

/**
 * A notification of the removal of a single entry. The key and/or value may be null if they were
 * already garbage collected. This class holds strong references to the key and value, regardless
 * of the type of references the cache may be using.
 *
 * @author ben.manes@gmail.com (Ben Manes)
 */
@Immutable
public final class RemovalNotification<K, V> implements Entry<K, V> {
  @Nullable private final K key;
  @Nullable private final V value;
  private final RemovalCause cause;

  /**
   * Creates an entry representing a mapping from the specified key to the specified value.
   *
   * @param key the key represented by this entry
   * @param value the value represented by this entry
   * @param cause the reason for which the entry was removed
   */
  public RemovalNotification(@Nullable K key, @Nullable V value, @Nonnull RemovalCause cause) {
    this.cause = requireNonNull(cause);
    this.value = value;
    this.key = key;
  }

  /**
   * @return the cause for which the entry was removed
   */
  @Nonnull
  public RemovalCause getCause() {
    return cause;
  }

  /**
   * Returns {@code true} if there was an automatic removal due to eviction (the cause is neither
   * {@link RemovalCause#EXPLICIT} nor {@link RemovalCause#REPLACED}).
   *
   * @return if the entry was removed due to eviction
   */
  public boolean wasEvicted() {
    return cause.wasEvicted();
  }

  /**
   * Returns the key of the removed entry or null if it was garbage collected due to
   * {@link Caffeine#weakKeys()} eviction.
   */
  @Override @Nullable
  public K getKey() {
    return key;
  }

  /**
   * Returns the key of the removed entry or null if it was garbage collected due to
   * {@link Caffeine#weakValues()} or {@link Caffeine#softValues()} eviction.
   */
  @Override @Nullable
  public V getValue() {
    return value;
  }

  @Override
  public V setValue(V value) {
    throw new UnsupportedOperationException();
  }

  @Override
  public boolean equals(Object o) {
    if (o == this) {
      return true;
    } else if (!(o instanceof Entry<?, ?>)) {
      return false;
    }
    Entry<?, ?> entry = (Entry<?, ?>) o;
    return Objects.equals(key, entry.getKey())
        && Objects.equals(value, entry.getValue());
  }

  @Override
  public int hashCode() {
    return ((key == null) ? 0 : key.hashCode()) ^ ((value == null) ? 0 : value.hashCode());
  }

  /** Returns a string representation of the form <code>{key}={value}</code>. */
  @Override
  public String toString() {
    return key + "=" + value;
  }
}
