/*
 * Copyright (C) 2008 Google Inc.
 *
 * 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 org.mapdb;

import java.util.concurrent.ConcurrentMap;

/**
 * Tests representing the contract of {@link ConcurrentMap}. Concrete
 * subclasses of this base class test conformance of concrete
 * {@link ConcurrentMap} subclasses to that contract.
 *
 * <p>The tests in this class for null keys and values only check maps for
 * which null keys and values are not allowed. There are currently no
 * {@link ConcurrentMap} implementations that support nulls.
 *
 * @author Jared Levy
 */
public abstract class ConcurrentMapInterfaceTest<K, V>
    extends MapInterfaceTest<K, V> {

  protected ConcurrentMapInterfaceTest(boolean allowsNullKeys,
      boolean allowsNullValues, boolean supportsPut, boolean supportsRemove,
      boolean supportsClear, boolean supportsIteratorRemove, boolean supportsEntrySetValue) {
    super(allowsNullKeys, allowsNullValues, supportsPut, supportsRemove,
        supportsClear,supportsIteratorRemove, supportsEntrySetValue);
  }

  /**
   * Creates a new value that is not expected to be found in
   * {@link #makePopulatedMap()} and differs from the value returned by
   * {@link #getValueNotInPopulatedMap()}.
   *
   * @return a value
   * @throws UnsupportedOperationException if it's not possible to make a value
   * that will not be found in the map
   */
  protected abstract V getSecondValueNotInPopulatedMap()
      throws UnsupportedOperationException;

  @Override protected abstract ConcurrentMap<K, V> makeEmptyMap()
      throws UnsupportedOperationException;

  @Override protected abstract ConcurrentMap<K, V> makePopulatedMap()
      throws UnsupportedOperationException;

  @Override protected ConcurrentMap<K, V> makeEitherMap() {
    try {
      return makePopulatedMap();
    } catch (UnsupportedOperationException e) {
      return makeEmptyMap();
    }
  }

  public void testPutIfAbsentNewKey() {
    final ConcurrentMap<K, V> map;
    final K keyToPut;
    final V valueToPut;
    try {
      map = makeEitherMap();
      keyToPut = getKeyNotInPopulatedMap();
      valueToPut = getValueNotInPopulatedMap();
    } catch (UnsupportedOperationException e) {
      return;
    }
    if (supportsPut) {
      int initialSize = map.size();
      V oldValue = map.putIfAbsent(keyToPut, valueToPut);
      assertEquals(valueToPut, map.get(keyToPut));
      assertTrue(map.containsKey(keyToPut));
      assertTrue(map.containsValue(valueToPut));

      assertEquals(initialSize + 1, map.size());
      assertNull(oldValue);
    } else {
      try {
        map.putIfAbsent(keyToPut, valueToPut);
        fail("Expected UnsupportedOperationException.");
      } catch (UnsupportedOperationException e) {
        // Expected.
      }
    }
    assertInvariants(map);
  }

  public void testPutIfAbsentExistingKey() {
    final ConcurrentMap<K, V> map;
    final K keyToPut;
    final V valueToPut;
    try {
      map = makePopulatedMap();
      valueToPut = getValueNotInPopulatedMap();
    } catch (UnsupportedOperationException e) {
      return;
    }
    keyToPut = map.keySet().iterator().next();
    if (supportsPut) {
      V oldValue = map.get(keyToPut);
      int initialSize = map.size();
      assertEquals(oldValue, map.putIfAbsent(keyToPut, valueToPut));
      assertEquals(oldValue, map.get(keyToPut));
      assertTrue(map.containsKey(keyToPut));
      assertTrue(map.containsValue(oldValue));
      assertFalse(map.containsValue(valueToPut));
      assertEquals(initialSize, map.size());
    } else {
      try {
        map.putIfAbsent(keyToPut, valueToPut);
        fail("Expected UnsupportedOperationException.");
      } catch (UnsupportedOperationException e) {
        // Expected.
      }
    }
    assertInvariants(map);
  }

  public void testPutIfAbsentNullKey() {
    if (allowsNullKeys) {
      return;   // Not yet implemented
    }
    final ConcurrentMap<K, V> map;
    final V valueToPut;
    try {
      map = makeEitherMap();
      valueToPut = getValueNotInPopulatedMap();
    } catch (UnsupportedOperationException e) {
      return;
    }
    int initialSize = map.size();
    if (supportsPut) {
      try {
        map.putIfAbsent(null, valueToPut);
        fail("Expected NullPointerException");
      } catch (NullPointerException e) {
        // Expected.
      }
    } else {
      try {
        map.putIfAbsent(null, valueToPut);
        fail("Expected UnsupportedOperationException or NullPointerException");
      } catch (UnsupportedOperationException e) {
        // Expected.
      } catch (NullPointerException e) {
        // Expected.
      }
    }
    assertEquals(initialSize, map.size());
    assertInvariants(map);
  }

  public void testPutIfAbsentNewKeyNullValue() {
    if (allowsNullValues) {
      return;   // Not yet implemented
    }
    final ConcurrentMap<K, V> map;
    final K keyToPut;
    try {
      map = makeEitherMap();
      keyToPut = getKeyNotInPopulatedMap();
    } catch (UnsupportedOperationException e) {
      return;
    }
    int initialSize = map.size();
    if (supportsPut) {
      try {
        map.putIfAbsent(keyToPut, null);
        fail("Expected NullPointerException");
      } catch (NullPointerException e) {
        // Expected.
      }
    } else {
      try {
        map.putIfAbsent(keyToPut, null);
        fail("Expected UnsupportedOperationException or NullPointerException");
      } catch (UnsupportedOperationException e) {
        // Expected.
      } catch (NullPointerException e) {
        // Expected.
      }
    }
    assertEquals(initialSize, map.size());
    assertInvariants(map);
  }


  public void testRemoveKeyValueExisting() {
    final ConcurrentMap<K, V> map;
    final K keyToRemove;
    try {
      map = makePopulatedMap();
    } catch (UnsupportedOperationException e) {
      return;
    }
    keyToRemove = map.keySet().iterator().next();
    V oldValue = map.get(keyToRemove);
    if (supportsRemove) {
      int initialSize = map.size();
      assertTrue(map.remove(keyToRemove, oldValue));
      assertFalse(map.containsKey(keyToRemove));
      assertEquals(initialSize - 1, map.size());
    } else {
      try {
        map.remove(keyToRemove, oldValue);
        fail("Expected UnsupportedOperationException.");
      } catch (UnsupportedOperationException e) {
        // Expected.
      }
    }
    assertInvariants(map);
  }

  public void testRemoveKeyValueMissingKey() {
    final ConcurrentMap<K, V> map;
    final K keyToRemove;
    final V valueToRemove;
    try {
      map = makePopulatedMap();
      keyToRemove = getKeyNotInPopulatedMap();
      valueToRemove = getValueNotInPopulatedMap();
    } catch (UnsupportedOperationException e) {
      return;
    }
    if (supportsRemove) {
      int initialSize = map.size();
      assertFalse(map.remove(keyToRemove, valueToRemove));
      assertEquals(initialSize, map.size());
    } else {
      try {
        map.remove(keyToRemove, valueToRemove);
        fail("Expected UnsupportedOperationException.");
      } catch (UnsupportedOperationException e) {
        // Expected.
      }
    }
    assertInvariants(map);
  }

  public void testRemoveKeyValueDifferentValue() {
    final ConcurrentMap<K, V> map;
    final K keyToRemove;
    final V valueToRemove;
    try {
      map = makePopulatedMap();
      valueToRemove = getValueNotInPopulatedMap();
    } catch (UnsupportedOperationException e) {
      return;
    }
    keyToRemove = map.keySet().iterator().next();
    if (supportsRemove) {
      int initialSize = map.size();
      V oldValue = map.get(keyToRemove);
      assertFalse(map.remove(keyToRemove, valueToRemove));
      assertEquals(oldValue, map.get(keyToRemove));
      assertTrue(map.containsKey(keyToRemove));
      assertEquals(initialSize, map.size());
    } else {
      try {
        map.remove(keyToRemove, valueToRemove);
        fail("Expected UnsupportedOperationException.");
      } catch (UnsupportedOperationException e) {
        // Expected.
      }
    }
    assertInvariants(map);
  }

  public void testRemoveKeyValueNullKey() {
    if (allowsNullKeys) {
      return;   // Not yet implemented
    }
    final ConcurrentMap<K, V> map;
    final V valueToRemove;
    try {
      map = makeEitherMap();
      valueToRemove = getValueNotInPopulatedMap();
    } catch (UnsupportedOperationException e) {
      return;
    }
    int initialSize = map.size();
    if (supportsRemove) {
      try {
        assertFalse(map.remove(null, valueToRemove));
      } catch (NullPointerException e) {
        // Optional.
      }
    } else {
      try {
        assertFalse(map.remove(null, valueToRemove));
      } catch (UnsupportedOperationException e) {
        // Optional.
      } catch (NullPointerException e) {
        // Optional.
      }
    }
    assertEquals(initialSize, map.size());
    assertInvariants(map);
  }

  public void testRemoveKeyValueExistingKeyNullValue() {
    if (allowsNullValues) {
      return;   // Not yet implemented
    }
    final ConcurrentMap<K, V> map;
    final K keyToRemove;
    try {
      map = makePopulatedMap();
    } catch (UnsupportedOperationException e) {
      return;
    }
    keyToRemove = map.keySet().iterator().next();
    int initialSize = map.size();
    if (supportsRemove) {
      try {
        assertFalse(map.remove(keyToRemove, null));
      } catch (NullPointerException e) {
        // Optional.
      }
    } else {
      try {
        assertFalse(map.remove(keyToRemove, null));
      } catch (UnsupportedOperationException e) {
        // Optional.
      } catch (NullPointerException e) {
        // Optional.
      }
    }
    assertEquals(initialSize, map.size());
    assertInvariants(map);
  }

  public void testRemoveKeyValueMissingKeyNullValue() {
    if (allowsNullValues) {
      return;   // Not yet implemented
    }
    final ConcurrentMap<K, V> map;
    final K keyToRemove;
    try {
      map = makeEitherMap();
      keyToRemove = getKeyNotInPopulatedMap();
    } catch (UnsupportedOperationException e) {
      return;
    }
    int initialSize = map.size();
    if (supportsRemove) {
      try {
        assertFalse(map.remove(keyToRemove, null));
      } catch (NullPointerException e) {
        // Optional.
      }
    } else {
      try {
        assertFalse(map.remove(keyToRemove, null));
      } catch (UnsupportedOperationException e) {
        // Optional.
      } catch (NullPointerException e) {
        // Optional.
      }
    }
    assertEquals(initialSize, map.size());
    assertInvariants(map);
  }

  /* Replace2 tests call 2-parameter replace(key, value) */

  public void testReplace2ExistingKey() {
    final ConcurrentMap<K, V> map;
    final K keyToReplace;
    final V newValue;
    try {
      map = makePopulatedMap();
      newValue = getValueNotInPopulatedMap();
    } catch (UnsupportedOperationException e) {
      return;
    }
    keyToReplace = map.keySet().iterator().next();
    if (supportsPut) {
      V oldValue = map.get(keyToReplace);
      int initialSize = map.size();
      assertEquals(oldValue, map.replace(keyToReplace, newValue));
      assertEquals(newValue, map.get(keyToReplace));
      assertTrue(map.containsKey(keyToReplace));
      assertTrue(map.containsValue(newValue));
      assertEquals(initialSize, map.size());
    } else {
      try {
        map.replace(keyToReplace, newValue);
        fail("Expected UnsupportedOperationException.");
      } catch (UnsupportedOperationException e) {
        // Expected.
      }
    }
    assertInvariants(map);
  }

  public void testReplace2MissingKey() {
    final ConcurrentMap<K, V> map;
    final K keyToReplace;
    final V newValue;
    try {
      map = makeEitherMap();
      keyToReplace = getKeyNotInPopulatedMap();
      newValue = getValueNotInPopulatedMap();
    } catch (UnsupportedOperationException e) {
      return;
    }
    if (supportsPut) {
      int initialSize = map.size();
      assertNull(map.replace(keyToReplace, newValue));
      assertNull(map.get(keyToReplace));
      assertFalse(map.containsKey(keyToReplace));
      assertFalse(map.containsValue(newValue));
      assertEquals(initialSize, map.size());
    } else {
      try {
        map.replace(keyToReplace, newValue);
        fail("Expected UnsupportedOperationException.");
      } catch (UnsupportedOperationException e) {
        // Expected.
      }
    }
    assertInvariants(map);
  }

  public void testReplace2NullKey() {
    if (allowsNullKeys) {
      return;   // Not yet implemented
    }
    final ConcurrentMap<K, V> map;
    final V valueToReplace;
    try {
      map = makeEitherMap();
      valueToReplace = getValueNotInPopulatedMap();
    } catch (UnsupportedOperationException e) {
      return;
    }
    int initialSize = map.size();
    if (supportsPut) {
      try {
        assertNull(map.replace(null, valueToReplace));
      } catch (NullPointerException e) {
        // Optional.
      }
    } else {
      try {
        assertNull(map.replace(null, valueToReplace));
      } catch (UnsupportedOperationException e) {
        // Optional.
      } catch (NullPointerException e) {
        // Optional.
      }
    }
    assertEquals(initialSize, map.size());
    assertInvariants(map);
  }

  public void testReplace2ExistingKeyNullValue() {
    if (allowsNullValues) {
      return;   // Not yet implemented
    }
    final ConcurrentMap<K, V> map;
    final K keyToReplace;
    try {
      map = makePopulatedMap();
    } catch (UnsupportedOperationException e) {
      return;
    }
    keyToReplace = map.keySet().iterator().next();
    int initialSize = map.size();
    if (supportsPut) {
      try {
        map.replace(keyToReplace, null);
        fail("Expected NullPointerException");
      } catch (NullPointerException e) {
        // Expected.
      }
    } else {
      try {
        map.replace(keyToReplace, null);
        fail("Expected UnsupportedOperationException or NullPointerException");
      } catch (UnsupportedOperationException e) {
        // Expected.
      } catch (NullPointerException e) {
        // Expected.
      }
    }
    assertEquals(initialSize, map.size());
    assertInvariants(map);
  }

  public void testReplace2MissingKeyNullValue() {
    if (allowsNullValues) {
      return;   // Not yet implemented
    }
    final ConcurrentMap<K, V> map;
    final K keyToReplace;
    try {
      map = makeEitherMap();
      keyToReplace = getKeyNotInPopulatedMap();
    } catch (UnsupportedOperationException e) {
      return;
    }
    int initialSize = map.size();
    if (supportsPut) {
      try {
        assertNull(map.replace(keyToReplace, null));
      } catch (NullPointerException e) {
        // Optional.
      }
    } else {
      try {
        assertNull(map.replace(keyToReplace, null));
      } catch (UnsupportedOperationException e) {
        // Optional.
      } catch (NullPointerException e) {
        // Optional.
      }
    }
    assertEquals(initialSize, map.size());
    assertInvariants(map);
  }

  /*
   * Replace3 tests call 3-parameter replace(key, oldValue, newValue)
   */

  public void testReplace3ExistingKeyValue() {
    final ConcurrentMap<K, V> map;
    final K keyToReplace;
    final V oldValue;
    final V newValue;
    try {
      map = makePopulatedMap();
      newValue = getValueNotInPopulatedMap();
    } catch (UnsupportedOperationException e) {
      return;
    }
    keyToReplace = map.keySet().iterator().next();
    oldValue = map.get(keyToReplace);
    if (supportsPut) {
      int initialSize = map.size();
      assertTrue(map.replace(keyToReplace, oldValue, newValue));
      assertEquals(newValue, map.get(keyToReplace));
      assertTrue(map.containsKey(keyToReplace));
      assertTrue(map.containsValue(newValue));
      assertFalse(map.containsValue(oldValue));
      assertEquals(initialSize, map.size());
    } else {
      try {
        map.replace(keyToReplace, oldValue, newValue);
        fail("Expected UnsupportedOperationException.");
      } catch (UnsupportedOperationException e) {
        // Expected.
      }
    }
    assertInvariants(map);
  }

  public void testReplace3ExistingKeyDifferentValue() {
    final ConcurrentMap<K, V> map;
    final K keyToReplace;
    final V oldValue;
    final V newValue;
    try {
      map = makePopulatedMap();
      oldValue = getValueNotInPopulatedMap();
      newValue = getSecondValueNotInPopulatedMap();
    } catch (UnsupportedOperationException e) {
      return;
    }
    keyToReplace = map.keySet().iterator().next();
    final V originalValue = map.get(keyToReplace);
    int initialSize = map.size();
    if (supportsPut) {
      assertFalse(map.replace(keyToReplace, oldValue, newValue));
    } else {
      try {
        map.replace(keyToReplace, oldValue, newValue);
        fail("Expected UnsupportedOperationException.");
      } catch (UnsupportedOperationException e) {
        // Expected.
      }
    }
    assertTrue(map.containsKey(keyToReplace));
    assertFalse(map.containsValue(newValue));
    assertFalse(map.containsValue(oldValue));
    assertEquals(originalValue, map.get(keyToReplace));
    assertEquals(initialSize, map.size());
    assertInvariants(map);
  }

  public void testReplace3MissingKey() {
    final ConcurrentMap<K, V> map;
    final K keyToReplace;
    final V oldValue;
    final V newValue;
    try {
      map = makeEitherMap();
      keyToReplace = getKeyNotInPopulatedMap();
      oldValue = getValueNotInPopulatedMap();
      newValue = getSecondValueNotInPopulatedMap();
    } catch (UnsupportedOperationException e) {
      return;
    }
    int initialSize = map.size();
    if (supportsPut) {
      assertFalse(map.replace(keyToReplace, oldValue, newValue));
    } else {
      try {
        map.replace(keyToReplace, oldValue, newValue);
        fail("Expected UnsupportedOperationException.");
      } catch (UnsupportedOperationException e) {
        // Expected.
      }
    }
    assertFalse(map.containsKey(keyToReplace));
    assertFalse(map.containsValue(newValue));
    assertFalse(map.containsValue(oldValue));
    assertEquals(initialSize, map.size());
    assertInvariants(map);
  }

  public void testReplace3NullKey() {
    if (allowsNullKeys) {
      return;   // Not yet implemented
    }
    final ConcurrentMap<K, V> map;
    final V oldValue;
    final V newValue;
    try {
      map = makeEitherMap();
      oldValue = getValueNotInPopulatedMap();
      newValue = getSecondValueNotInPopulatedMap();
    } catch (UnsupportedOperationException e) {
      return;
    }
    int initialSize = map.size();
    if (supportsPut) {
      try {
        assertFalse(map.replace(null, oldValue, newValue));
      } catch (NullPointerException e) {
        // Optional.
      }
    } else {
      try {
        assertFalse(map.replace(null, oldValue, newValue));
      } catch (UnsupportedOperationException e) {
        // Optional.
      } catch (NullPointerException e) {
        // Optional.
      }
    }
    assertEquals(initialSize, map.size());
    assertInvariants(map);
  }

  public void testReplace3ExistingKeyNullOldValue() {
    if (allowsNullValues) {
      return;   // Not yet implemented
    }
    final ConcurrentMap<K, V> map;
    final K keyToReplace;
    final V newValue;
    try {
      map = makePopulatedMap();
      newValue = getValueNotInPopulatedMap();
    } catch (UnsupportedOperationException e) {
      return;
    }
    keyToReplace = map.keySet().iterator().next();
    final V originalValue = map.get(keyToReplace);
    int initialSize = map.size();
    if (supportsPut) {
      try {
        assertFalse(map.replace(keyToReplace, null, newValue));
      } catch (NullPointerException e) {
        // Optional.
      }
    } else {
      try {
        assertFalse(map.replace(keyToReplace, null, newValue));
      } catch (UnsupportedOperationException e) {
        // Optional.
      } catch (NullPointerException e) {
        // Optional.
      }
    }
    assertEquals(initialSize, map.size());
    assertEquals(originalValue, map.get(keyToReplace));
    assertInvariants(map);
  }

  public void testReplace3MissingKeyNullOldValue() {
    if (allowsNullValues) {
      return;   // Not yet implemented
    }
    final ConcurrentMap<K, V> map;
    final K keyToReplace;
    final V newValue;
    try {
      map = makeEitherMap();
      keyToReplace = getKeyNotInPopulatedMap();
      newValue = getValueNotInPopulatedMap();
    } catch (UnsupportedOperationException e) {
      return;
    }
    int initialSize = map.size();
    if (supportsPut) {
      try {
        assertFalse(map.replace(keyToReplace, null, newValue));
      } catch (NullPointerException e) {
        // Optional.
      }
    } else {
      try {
        assertFalse(map.replace(keyToReplace, null, newValue));
      } catch (UnsupportedOperationException e) {
        // Optional.
      } catch (NullPointerException e) {
        // Optional.
      }
    }
    assertEquals(initialSize, map.size());
    assertInvariants(map);
  }

  public void testReplace3MissingKeyNullNewValue() {
    if (allowsNullValues) {
      return;   // Not yet implemented
    }
    final ConcurrentMap<K, V> map;
    final K keyToReplace;
    final V oldValue;
    try {
      map = makeEitherMap();
      keyToReplace = getKeyNotInPopulatedMap();
      oldValue = getValueNotInPopulatedMap();
    } catch (UnsupportedOperationException e) {
      return;
    }
    int initialSize = map.size();
    if (supportsPut) {
      try {
        map.replace(keyToReplace, oldValue, null);
      } catch (NullPointerException e) {
        // Optional.
      }
    } else {
      try {
        map.replace(keyToReplace, oldValue, null);
      } catch (UnsupportedOperationException e) {
        // Optional.
      } catch (NullPointerException e) {
        // Optional.
      }
    }
    assertEquals(initialSize, map.size());
    assertInvariants(map);
  }

  public void testReplace3ExistingKeyValueNullNewValue() {
    if (allowsNullValues) {
      return;   // Not yet implemented
    }
    final ConcurrentMap<K, V> map;
    final K keyToReplace;
    final V oldValue;
    try {
      map = makePopulatedMap();
    } catch (UnsupportedOperationException e) {
      return;
    }
    keyToReplace = map.keySet().iterator().next();
    oldValue = map.get(keyToReplace);
    int initialSize = map.size();
    if (supportsPut) {
      try {
        map.replace(keyToReplace, oldValue, null);
        fail("Expected NullPointerException");
      } catch (NullPointerException e) {
        // Expected.
      }
    } else {
      try {
        map.replace(keyToReplace, oldValue, null);
        fail("Expected UnsupportedOperationException or NullPointerException");
      } catch (UnsupportedOperationException e) {
        // Expected.
      } catch (NullPointerException e) {
        // Expected.
      }
    }
    assertEquals(initialSize, map.size());
    assertEquals(oldValue, map.get(keyToReplace));
    assertInvariants(map);
  }
}
