/*
 * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
 * agreements. See the NOTICE file distributed with this work for additional information regarding
 * copyright ownership. The ASF licenses this file to You 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.apache.geode.internal.offheap;

import static org.junit.Assert.*;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicInteger;

import org.junit.Rule;
import org.junit.Test;
import org.junit.contrib.java.lang.system.SystemOutRule;
import org.junit.experimental.categories.Category;
import org.junit.runner.RunWith;

import static org.mockito.Mockito.*;

import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PowerMockIgnore;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.*;

import org.apache.geode.internal.cache.RegionEntry;
import org.apache.geode.test.junit.categories.UnitTest;

/*
 * PowerMock used in this test to verify static method MemoryAllocatorImpl.debugLog
 */
@Category(UnitTest.class)
@RunWith(PowerMockRunner.class)
@PowerMockIgnore({"*.UnitTest"})
@PrepareForTest({MemoryAllocatorImpl.class})
public class ReferenceCountHelperImplTest {

  ReferenceCountHelperImpl rchi;

  @Rule
  public SystemOutRule sor = new SystemOutRule();

  @Test
  public void doTrackReferenceCountsWithTrackRefsTrueAndTrackFreesTrue() {
    rchi = getTrueTrue();
    assertTrue(rchi.trackReferenceCounts());
  }

  @Test
  public void doTrackReferenceCountsWithTrackRefsTrueAndTrackFreesFalse() {
    rchi = getTrueFalse();
    assertTrue(rchi.trackReferenceCounts());
  }

  @Test
  public void doTrackReferenceCountsWithTrackRefsFalseAndTrackFreesTrue() {
    rchi = getFalseTrue();
    assertFalse(rchi.trackReferenceCounts());
  }

  @Test
  public void doTrackReferenceCountsWithTrackRefsFalseAndTrackFreesFalse() {
    rchi = getFalseFalse();
    assertFalse(rchi.trackReferenceCounts());
  }

  @Test
  public void doTrackFreedReferenceCountsWithTrackRefsTrueAndTrackFreesTrue() {
    rchi = getTrueTrue();
    assertTrue(rchi.trackFreedReferenceCounts());
  }

  @Test
  public void doTrackFreedReferenceCountsWithTrackRefsTrueAndTrackFreesFalse() {
    rchi = getTrueFalse();
    assertFalse(rchi.trackFreedReferenceCounts());
  }

  @Test
  public void doTrackFreedReferenceCountsWithTrackRefsFalseAndTrackFreesTrue() {
    rchi = getFalseTrue();
    assertTrue(rchi.trackFreedReferenceCounts());
  }

  @Test
  public void doTrackFreedReferenceCountsWithTrackRefsFalseAndTrackFreesFalse() {
    rchi = getFalseFalse();
    assertFalse(rchi.trackFreedReferenceCounts());
  }

  @Test
  public void doSkipRefCountTrackingWithTrackRefsTrueAndTrackFreesTrue() {
    rchi = getTrueTrue();
    Object preOwner = rchi.getReferenceCountOwner();

    rchi.skipRefCountTracking();
    Object postOwner = rchi.getReferenceCountOwner();

    assertTrue(postOwner != preOwner); // skip sets owner to SKIP_REF_COUNT_TRACKING

    assertFalse(rchi.isRefCountTracking());

    Long address = (long) 0x1000;
    boolean decRefCount = false;
    int rc = 1;
    rchi.refCountChanged(address, decRefCount, rc);
    List<RefCountChangeInfo> list = rchi.peekRefCountInfo(address);
    assertEquals(null, list);

    rchi.unskipRefCountTracking();
    postOwner = rchi.getReferenceCountOwner();
    assertEquals(postOwner, preOwner);

    assertTrue(rchi.isRefCountTracking());
  }

  @Test
  public void doSkipRefCountTrackingWithTrackRefsFalseAndTrackFreesTrue() {
    rchi = getFalseTrue();
    Object preOwner = rchi.getReferenceCountOwner();
    assertEquals(null, preOwner); // getReferenceCountOwner returns null if not tracking

    rchi.skipRefCountTracking();
    assertFalse(rchi.isRefCountTracking());

    rchi.unskipRefCountTracking();
    assertFalse(rchi.isRefCountTracking()); // system prop not set
  }

  @Test
  public void doSkipRefCountTrackingWithTrackRefsFalseAndTrackFreesFalse() {
    rchi = getFalseFalse();
    Object preOwner = rchi.getReferenceCountOwner();
    assertEquals(null, preOwner); // getReferenceCountOwner returns null if not tracking

    rchi.skipRefCountTracking();
    assertFalse(rchi.isRefCountTracking());

    rchi.unskipRefCountTracking();
    assertFalse(rchi.isRefCountTracking()); // system prop not set
  }

  @Test
  public void doSkipRefCountTrackingWithTrackRefsTrueAndTrackFreesFalse() {
    rchi = getTrueFalse();
    Object preOwner = rchi.getReferenceCountOwner();

    rchi.skipRefCountTracking();
    Object postOwner = rchi.getReferenceCountOwner();

    assertTrue(postOwner != preOwner); // skip sets owner to SKIP_REF_COUNT_TRACKING

    assertFalse(rchi.isRefCountTracking());

    rchi.unskipRefCountTracking();
    postOwner = rchi.getReferenceCountOwner();
    assertEquals(postOwner, preOwner);

    assertTrue(rchi.isRefCountTracking());
  }

  @Test
  public void doSetReferenceCountOwnerWithTrackRefsTrueAndTrackFreesTrue() {
    rchi = getTrueTrue();
    String owner = null;
    rchi.setReferenceCountOwner(owner);
    AtomicInteger ai = rchi.getReenterCount();
    assertEquals(0, ai.get());

    owner = new String("SomeOwner");
    rchi.setReferenceCountOwner(owner);
    ai = rchi.getReenterCount();
    assertEquals(1, ai.get());
    assertEquals(rchi.getReferenceCountOwner(), owner);

    String owner2 = new String("SomeOwner2");
    rchi.setReferenceCountOwner(owner2);
    ai = rchi.getReenterCount();
    assertEquals(2, ai.get());
    assertTrue(rchi.getReferenceCountOwner() != owner2); // stays original owner until cnt = 0

    String owner3 = null;
    rchi.setReferenceCountOwner(owner3);
    ai = rchi.getReenterCount();
    assertEquals(1, ai.get());
    assertEquals(rchi.getReferenceCountOwner(), owner);

    owner = null;
    rchi.setReferenceCountOwner(owner);
    ai = rchi.getReenterCount();
    assertEquals(0, ai.get());
    assertEquals(rchi.getReferenceCountOwner(), null);

    RegionEntry re = mock(RegionEntry.class);
    rchi.setReferenceCountOwner(re);
    ai = rchi.getReenterCount();
    assertEquals(1, ai.get());
    assertEquals(rchi.getReferenceCountOwner(), re);

    Long address = (long) 0x1000;
    boolean decRefCount = false;
    int rc = 1;

    rchi.refCountChanged(address, decRefCount, rc);
    List<RefCountChangeInfo> list = rchi.peekRefCountInfo(address);
    assertEquals(1, list.size());
    RefCountChangeInfo rcci = list.get(0);
    assertEquals(0, rcci.getUseCount()); // line 258 of ref cnt helper does not set useCount = 1
                                         // when adding new entry?

    rchi.refCountChanged(address, decRefCount, rc);
    list = rchi.peekRefCountInfo(address);
    assertEquals(1, list.size());
    rcci = list.get(0);
    assertEquals(1, rcci.getUseCount()); // line 258 of ref cnt helper does not set useCount = 1
                                         // when adding new entry?

    decRefCount = true;
    rchi.refCountChanged(address, decRefCount, rc);
    list = rchi.peekRefCountInfo(address);
    assertEquals(1, list.size());
    rcci = list.get(0);
    assertEquals(0, rcci.getUseCount()); // line 258 of ref cnt helper does not set useCount = 1
                                         // when adding new entry?

    rchi.refCountChanged(address, decRefCount, rc);
    list = rchi.peekRefCountInfo(address);
    assertEquals(0, list.size());

  }

  @Test
  public void doSetReferenceCountOwnerWithTrackRefsFalseAndTrackFreesTrue() {
    rchi = getFalseTrue();
    String owner = null;
    rchi.setReferenceCountOwner(owner);
    assertEquals(rchi.getReferenceCountOwner(), owner);
    AtomicInteger ai = rchi.getReenterCount();
    assertEquals(null, ai);
  }

  @Test
  public void doSetReferenceCountOwnerWithTrackRefsFalseAndTrackFreesFalse() {
    rchi = getFalseFalse();
    String owner = null;
    rchi.setReferenceCountOwner(owner);
    assertEquals(rchi.getReferenceCountOwner(), owner);
    AtomicInteger ai = rchi.getReenterCount();
    assertEquals(null, ai);
  }

  @Test
  public void doSetReferenceCountOwnerWithTrackRefsTrueAndTrackFreesFalse() {
    rchi = getTrueFalse();
    String owner = null;
    rchi.setReferenceCountOwner(owner);
    AtomicInteger ai = rchi.getReenterCount();
    assertEquals(0, ai.get());

    owner = new String("SomeOwner");
    rchi.setReferenceCountOwner(owner);
    ai = rchi.getReenterCount();
    assertEquals(1, ai.get());
    assertEquals(rchi.getReferenceCountOwner(), owner);

    String owner2 = new String("SomeOwner2");
    rchi.setReferenceCountOwner(owner2);
    ai = rchi.getReenterCount();
    assertEquals(2, ai.get());
    assertTrue(rchi.getReferenceCountOwner() != owner2); // stays original owner until cnt = 0

    String owner3 = null;
    rchi.setReferenceCountOwner(owner3);
    ai = rchi.getReenterCount();
    assertEquals(1, ai.get());
    assertEquals(rchi.getReferenceCountOwner(), owner);

    owner = null;
    rchi.setReferenceCountOwner(owner);
    ai = rchi.getReenterCount();
    assertEquals(0, ai.get());
    assertEquals(rchi.getReferenceCountOwner(), null);
  }

  @Test
  public void doCreateReferenceCountOwnerWithTrackRefsTrueAndTrackFreesTrue() {
    rchi = getTrueTrue();
    Object owner = rchi.createReferenceCountOwner();
    assertFalse(owner == null);
    AtomicInteger ai = rchi.getReenterCount();
    assertEquals(1, ai.get());

    owner = null;
    rchi.setReferenceCountOwner(owner);
    ai = rchi.getReenterCount();
    assertEquals(0, ai.get());
    assertEquals(rchi.getReferenceCountOwner(), null);
  }

  @Test
  public void doCreateReferenceCountOwnerWithTrackRefsFalseAndTrackFreesTrue() {
    rchi = getFalseTrue();
    Object owner = rchi.createReferenceCountOwner();
    assertTrue(owner == null);
  }

  @Test
  public void doCreateReferenceCountOwnerWithTrackRefsFalseAndTrackFreesFalse() {
    rchi = getFalseFalse();
    Object owner = rchi.createReferenceCountOwner();
    assertTrue(owner == null);
  }

  @Test
  public void doCreateReferenceCountOwnerWithTrackRefsTrueAndTrackFreesFalse() {
    rchi = getTrueFalse();
    Object owner = rchi.createReferenceCountOwner();
    assertFalse(owner == null);
    AtomicInteger ai = rchi.getReenterCount();
    assertEquals(1, ai.get());

    owner = null;
    rchi.setReferenceCountOwner(owner);
    ai = rchi.getReenterCount();
    assertEquals(0, ai.get());
    assertEquals(rchi.getReferenceCountOwner(), null);
  }

  @Test
  public void doRefCountChangedNoOwnerWithTrackRefsTrueAndTrackFreesTrue() {
    rchi = getTrueTrue();
    Long address = (long) 0x1000;
    boolean decRefCount = false;
    int rc = 1;

    rchi.freeRefCountInfo(address); // quick check of free of nonexistent info

    Object owner = rchi.getReferenceCountOwner();
    assertTrue(owner == null);

    rchi.refCountChanged(address, decRefCount, rc);
    List<RefCountChangeInfo> list = rchi.peekRefCountInfo(address);
    assertEquals(1, list.size());
    RefCountChangeInfo rcci = list.get(0);
    assertEquals(0, rcci.getUseCount()); // line 258 of ref cnt helper does not set useCount = 1
                                         // when adding new entry?

    rchi.refCountChanged(address, decRefCount, rc);
    list = rchi.peekRefCountInfo(address);
    assertEquals(1, list.size());
    rcci = list.get(0);
    assertEquals(1, rcci.getUseCount()); // line 258 of ref cnt helper does not set useCount = 1
                                         // when adding new entry?

    decRefCount = true;
    rchi.refCountChanged(address, decRefCount, rc);
    list = rchi.peekRefCountInfo(address);
    assertEquals(2, list.size()); // inc and dec are tracked in different changeinfo objects (?)
    rcci = list.get(1);
    assertEquals(0, rcci.getUseCount()); // line 258 of ref cnt helper does not set useCount = 1
                                         // when adding new entry?

    rchi.refCountChanged(address, decRefCount, rc);
    list = rchi.peekRefCountInfo(address);
    assertEquals(2, list.size());
    rcci = list.get(1);
    assertEquals(1, rcci.getUseCount()); // line 258 of ref cnt helper does not set useCount = 1
                                         // when adding new entry?

    list = rchi.peekRefCountInfo(address);
    assertEquals(2, list.size()); // list contains 2 entries from inc/dec done above

    List<RefCountChangeInfo> freeInfo = rchi.getFreeRefCountInfo(address);
    assertEquals(null, freeInfo); // no freeRefCountInfo calls yet

    rchi.freeRefCountInfo(address); // when freed, moved to FreeRefCountInfo list

    List<RefCountChangeInfo> freeInfo2 = rchi.getFreeRefCountInfo(address);
    assertEquals(2, freeInfo2.size()); // the inc/dec info moved to freeRefCountInfo list

    list = rchi.getRefCountInfo(address);
    assertEquals(null, list); // the inc/dec ref count list should now be null
  }

  @Test
  public void doRefCountChangedNoOwnerWithTrackRefsFalseAndTrackFreesTrue() {

    rchi = getFalseTrue();

    Long address = (long) 0x1000;
    boolean decRefCount = false;
    int rc = 1;

    Object owner = rchi.getReferenceCountOwner();
    assertTrue(owner == null);

    rchi.refCountChanged(address, decRefCount, rc);
    List<RefCountChangeInfo> list = rchi.peekRefCountInfo(address);
    assertEquals(null, list);

    decRefCount = true;
    rchi.refCountChanged(address, decRefCount, rc);
    list = rchi.peekRefCountInfo(address);
    assertEquals(null, list);

    List<RefCountChangeInfo> freeInfo = rchi.getFreeRefCountInfo(address);
    assertEquals(null, freeInfo); // no freeRefCountInfo calls yet

    rchi.freeRefCountInfo(address); // noop when not tracking

    freeInfo = rchi.getFreeRefCountInfo(address);
    assertEquals(null, freeInfo); // should still be null
  }

  @Test
  public void doRefCountChangedNoOwnerWithTrackRefsFalseAndTrackFreesFalse() {

    rchi = getFalseFalse();

    Long address = (long) 0x1000;
    boolean decRefCount = false;
    int rc = 1;

    Object owner = rchi.getReferenceCountOwner();
    assertTrue(owner == null);

    rchi.refCountChanged(address, decRefCount, rc);
    List<RefCountChangeInfo> list = rchi.peekRefCountInfo(address);
    assertEquals(null, list);

    decRefCount = true;
    rchi.refCountChanged(address, decRefCount, rc);
    list = rchi.peekRefCountInfo(address);
    assertEquals(null, list);

    List<RefCountChangeInfo> freeInfo = rchi.getFreeRefCountInfo(address);
    assertEquals(null, freeInfo); // no freeRefCountInfo calls yet

    rchi.freeRefCountInfo(address); // noop when not tracking

    freeInfo = rchi.getFreeRefCountInfo(address);
    assertEquals(null, freeInfo); // should still be null
  }

  @Test
  public void doRefCountChangedNoOwnerWithTrackRefsTrueAndTrackFreesFalse() {
    rchi = getTrueFalse();
    Long address = (long) 0x1000;
    boolean decRefCount = false;
    int rc = 1;

    Object owner = rchi.getReferenceCountOwner();
    assertTrue(owner == null);

    rchi.refCountChanged(address, decRefCount, rc);
    List<RefCountChangeInfo> list = rchi.peekRefCountInfo(address);
    assertEquals(1, list.size());
    RefCountChangeInfo rcci = list.get(0);
    assertEquals(0, rcci.getUseCount()); // line 258 of ref cnt helper does not set useCount = 1
                                         // when adding new entry?

    rchi.refCountChanged(address, decRefCount, rc);
    list = rchi.peekRefCountInfo(address);
    assertEquals(1, list.size());
    rcci = list.get(0);
    assertEquals(1, rcci.getUseCount()); // line 258 of ref cnt helper does not set useCount = 1
                                         // when adding new entry?

    decRefCount = true;
    rchi.refCountChanged(address, decRefCount, rc);
    list = rchi.peekRefCountInfo(address);
    assertEquals(2, list.size()); // inc and dec are tracked in different changeinfo objects (?)
    rcci = list.get(1);
    assertEquals(0, rcci.getUseCount()); // line 258 of ref cnt helper does not set useCount = 1
                                         // when adding new entry?

    rchi.refCountChanged(address, decRefCount, rc);
    list = rchi.peekRefCountInfo(address);
    assertEquals(2, list.size());
    rcci = list.get(1);
    assertEquals(1, rcci.getUseCount()); // line 258 of ref cnt helper does not set useCount = 1
                                         // when adding new entry?

    list = rchi.peekRefCountInfo(address);
    assertEquals(2, list.size()); // list contains 2 entries from inc/dec done above

    List<RefCountChangeInfo> freeInfo = rchi.getFreeRefCountInfo(address);
    assertEquals(null, freeInfo); // no freeRefCountInfo calls yet

    rchi.freeRefCountInfo(address); // when freed, moved to FreeRefCountInfo list

    List<RefCountChangeInfo> freeInfo2 = rchi.getFreeRefCountInfo(address);
    assertEquals(null, freeInfo2); // not tracking freed info

    list = rchi.getRefCountInfo(address);
    assertEquals(null, list); // the inc/dec ref count list should now be null
  }

  @Test
  public void doRefCountChangedWithOwnerWithTrackRefsTrueAndTrackFreesTrue() {
    rchi = getTrueTrue();
    Long address = (long) 0x1000;
    boolean decRefCount = false;
    int rc = 1;

    Object owner = rchi.createReferenceCountOwner();
    assertFalse(owner == null);

    AtomicInteger ai = rchi.getReenterCount();
    assertEquals(1, ai.get());

    owner = null;
    rchi.setReferenceCountOwner(owner);
    ai = rchi.getReenterCount();
    assertEquals(0, ai.get());
    assertEquals(rchi.getReferenceCountOwner(), null);

    owner = rchi.createReferenceCountOwner();
    assertFalse(owner == null);

    ai = rchi.getReenterCount();
    assertEquals(1, ai.get());

    rchi.refCountChanged(address, decRefCount, rc);
    List<RefCountChangeInfo> list = rchi.peekRefCountInfo(address);
    assertEquals(1, list.size());
    RefCountChangeInfo rcci = list.get(0);
    assertEquals(0, rcci.getUseCount()); // line 258 of ref cnt helper does not set useCount = 1
                                         // when adding new entry?

    rchi.refCountChanged(address, decRefCount, rc);
    list = rchi.peekRefCountInfo(address);
    assertEquals(1, list.size());
    rcci = list.get(0);
    assertEquals(1, rcci.getUseCount()); // line 258 of ref cnt helper does not set useCount = 1
                                         // when adding new entry?

    decRefCount = true;
    rchi.refCountChanged(address, decRefCount, rc);
    list = rchi.peekRefCountInfo(address);
    assertEquals(1, list.size()); // inc and dec are tracked in different changeinfo objects (?)
    rcci = list.get(0);
    assertEquals(0, rcci.getUseCount()); // line 258 of ref cnt helper does not set useCount = 1
                                         // when adding new entry?

    rchi.refCountChanged(address, decRefCount, rc);
    list = rchi.getRefCountInfo(address);
    assertEquals(0, list.size());
  }

  @Test
  public void doRefCountChangedWithOwnerWithTrackRefsFalseAndTrackFreesTrue() {

    rchi = getFalseTrue();

    Long address = (long) 0x1000;
    boolean decRefCount = false;
    int rc = 1;

    Object owner = rchi.createReferenceCountOwner();
    assertTrue(owner == null);

    rchi.refCountChanged(address, decRefCount, rc);
    List<RefCountChangeInfo> list = rchi.peekRefCountInfo(address);
    assertEquals(null, list);

    rchi.refCountChanged(address, decRefCount, rc);
    list = rchi.peekRefCountInfo(address);
    assertEquals(null, list);

    decRefCount = true;
    rchi.refCountChanged(address, decRefCount, rc);
    list = rchi.peekRefCountInfo(address);
    assertEquals(null, list); // inc and dec are tracked in different changeinfo objects (?)

    rchi.refCountChanged(address, decRefCount, rc);
    list = rchi.peekRefCountInfo(address);
    assertEquals(null, list);

    List<RefCountChangeInfo> freeInfo = rchi.getFreeRefCountInfo(address);
    assertEquals(null, freeInfo); // no freeRefCountInfo calls yet

    rchi.freeRefCountInfo(address); // when freed, moved to FreeRefCountInfo list

    freeInfo = rchi.getFreeRefCountInfo(address);
    assertEquals(null, freeInfo); // the inc/dec info moved to freeRefCountInfo list
  }

  @Test
  public void doRefCountChangedWithOwnerWithTrackRefsFalseAndTrackFreesFalse() {

    rchi = getFalseFalse();

    Long address = (long) 0x1000;
    boolean decRefCount = false;
    int rc = 1;

    Object owner = rchi.createReferenceCountOwner();
    assertTrue(owner == null);

    rchi.refCountChanged(address, decRefCount, rc);
    List<RefCountChangeInfo> list = rchi.peekRefCountInfo(address);
    assertEquals(null, list);

    rchi.refCountChanged(address, decRefCount, rc);
    list = rchi.peekRefCountInfo(address);
    assertEquals(null, list);

    decRefCount = true;
    rchi.refCountChanged(address, decRefCount, rc);
    list = rchi.peekRefCountInfo(address);
    assertEquals(null, list); // inc and dec are tracked in different changeinfo objects (?)

    rchi.refCountChanged(address, decRefCount, rc);
    list = rchi.peekRefCountInfo(address);
    assertEquals(null, list);

    List<RefCountChangeInfo> freeInfo = rchi.getFreeRefCountInfo(address);
    assertEquals(null, freeInfo); // no freeRefCountInfo calls yet

    rchi.freeRefCountInfo(address); // when freed, moved to FreeRefCountInfo list

    freeInfo = rchi.getFreeRefCountInfo(address);
    assertEquals(null, freeInfo); // the inc/dec info moved to freeRefCountInfo list
  }

  @Test
  public void doRefCountChangedWithOwnerWithTrackRefsTrueAndTrackFreesFalse() {
    rchi = getTrueFalse();
    Long address = (long) 0x1000;
    boolean decRefCount = false;
    int rc = 1;

    Object owner = rchi.createReferenceCountOwner();
    assertFalse(owner == null);

    AtomicInteger ai = rchi.getReenterCount();
    assertEquals(1, ai.get());

    owner = null;
    rchi.setReferenceCountOwner(owner);
    ai = rchi.getReenterCount();
    assertEquals(0, ai.get());
    assertEquals(rchi.getReferenceCountOwner(), null);

    owner = rchi.createReferenceCountOwner();
    assertFalse(owner == null);

    ai = rchi.getReenterCount();
    assertEquals(1, ai.get());

    rchi.refCountChanged(address, decRefCount, rc);
    List<RefCountChangeInfo> list = rchi.peekRefCountInfo(address);
    assertEquals(1, list.size());
    RefCountChangeInfo rcci = list.get(0);
    assertEquals(0, rcci.getUseCount()); // line 258 of ref cnt helper does not set useCount = 1
                                         // when adding new entry?

    rchi.refCountChanged(address, decRefCount, rc);
    list = rchi.peekRefCountInfo(address);
    assertEquals(1, list.size());
    rcci = list.get(0);
    assertEquals(1, rcci.getUseCount()); // line 258 of ref cnt helper does not set useCount = 1
                                         // when adding new entry?

    decRefCount = true;
    rchi.refCountChanged(address, decRefCount, rc);
    list = rchi.peekRefCountInfo(address);
    assertEquals(1, list.size()); // inc and dec are tracked in different changeinfo objects (?)
    rcci = list.get(0);
    assertEquals(0, rcci.getUseCount()); // line 258 of ref cnt helper does not set useCount = 1
                                         // when adding new entry?

    rchi.refCountChanged(address, decRefCount, rc);
    list = rchi.peekRefCountInfo(address);
    assertEquals(0, list.size());
  }

  @Test
  public void doGetRefCountInfoWithTrackRefsTrueAndTrackFreesTrue() {
    rchi = getTrueTrue();
    long address = (long) 0x1000;
    boolean decRefCount = false;
    int rc = 1;

    List<RefCountChangeInfo> list = null;

    rchi.refCountChanged(address, decRefCount, rc);
    list = rchi.peekRefCountInfo(address);
    assertEquals(1, list.size());
    RefCountChangeInfo rcci = list.get(0);
    assertEquals(0, rcci.getUseCount()); // line 258 of ref cnt helper does not set useCount = 1
                                         // when adding new entry?

    rchi.refCountChanged(address, decRefCount, rc);
    list = rchi.peekRefCountInfo(address);
    assertEquals(1, list.size());
    rcci = list.get(0);
    assertEquals(1, rcci.getUseCount()); // line 258 of ref cnt helper does not set useCount = 1
                                         // when adding new entry?

    List<RefCountChangeInfo> info = rchi.getRefCountInfo(address); // now getRefCountInfo
    assertEquals(1, info.size());
    rcci = info.get(0);
    assertEquals(1, rcci.getUseCount());

    list = rchi.peekRefCountInfo(address);
    assertEquals(0, list.size()); // getRefCountInfo leaves list LOCKED (i.e. empty)
  }

  @Test
  public void doRefCountChangedAfterGetRefCountInfoWithTrackRefsTrueAndTrackFreesTrue()
      throws Exception {
    rchi = getTrueTrue();
    long address = (long) 0x1000;
    boolean decRefCount = false;
    int rc = 1;

    List<RefCountChangeInfo> list = null;

    rchi.refCountChanged(address, decRefCount, rc);
    list = rchi.peekRefCountInfo(address);
    assertEquals(1, list.size());
    RefCountChangeInfo rcci = list.get(0);
    assertEquals(0, rcci.getUseCount()); // line 258 of ref cnt helper does not set useCount = 1
                                         // when adding new entry?

    rchi.refCountChanged(address, decRefCount, rc);
    list = rchi.peekRefCountInfo(address);
    assertEquals(1, list.size());
    rcci = list.get(0);
    assertEquals(1, rcci.getUseCount()); // line 258 of ref cnt helper does not set useCount = 1
                                         // when adding new entry?

    List<RefCountChangeInfo> info = rchi.getRefCountInfo(address); // now getRefCountInfo
    assertEquals(1, info.size());
    rcci = info.get(0);
    assertEquals(1, rcci.getUseCount());

    list = rchi.peekRefCountInfo(address);
    assertEquals(0, list.size()); // getRefCountInfo leaves list LOCKED (i.e. empty)

    sor.mute(); // Mute system out

    PowerMockito.spy(MemoryAllocatorImpl.class); // Watch the impl for invocation of debugLog

    rchi.refCountChanged(address, decRefCount, rc); // this line should fail. no inc after getInfo
                                                    // allowed
    PowerMockito.verifyStatic();
    MemoryAllocatorImpl.debugLog("refCount inced after orphan detected for @1000", true);

    decRefCount = true;

    rchi.refCountChanged(address, decRefCount, rc); // this line should fail. no inc after getInfo
                                                    // allowed
    PowerMockito.verifyStatic();
    MemoryAllocatorImpl.debugLog("refCount deced after orphan detected for @1000", true);

    rchi.freeRefCountInfo(address); // this line should fail. no free after getInfo allowed
    PowerMockito.verifyStatic();
    MemoryAllocatorImpl.debugLog("freed after orphan detected for @1000", true);

  }

  @Test
  public void doGetRefCountInfoWithTrackRefsFalseAndTrackFreesTrue() {
    rchi = getFalseTrue();
    long address = (long) 0x1000;
    boolean decRefCount = false;
    int rc = 1;

    List<RefCountChangeInfo> list = rchi.getRefCountInfo(address);
    assertEquals(null, list);

    rchi.refCountChanged(address, decRefCount, rc);
    list = rchi.peekRefCountInfo(address);
    assertEquals(null, list);

    List<RefCountChangeInfo> info = rchi.getRefCountInfo(address);
    assertEquals(null, info);

    list = rchi.peekRefCountInfo(address);
    assertEquals(null, list);

    rchi.refCountChanged(address, decRefCount, rc); // this will be ignored.
    decRefCount = true;
    rchi.refCountChanged(address, decRefCount, rc); // this will be ignored.
    rchi.freeRefCountInfo(address); // this will be ignored.

  }

  @Test
  public void doGetRefCountInfoWithTrackRefsFalseAndTrackFreesFalse() {
    rchi = getFalseFalse();
    long address = (long) 0x1000;
    boolean decRefCount = false;
    int rc = 1;

    List<RefCountChangeInfo> list = rchi.getRefCountInfo(address);
    assertEquals(null, list);

    rchi.refCountChanged(address, decRefCount, rc);
    list = rchi.peekRefCountInfo(address);
    assertEquals(null, list);

    List<RefCountChangeInfo> info = rchi.getRefCountInfo(address);
    assertEquals(null, info);

    list = rchi.peekRefCountInfo(address);
    assertEquals(null, list);
  }

  @Test
  public void doGetRefCountInfoWithTrackRefsTrueAndTrackFreesFalse() {
    rchi = getTrueFalse();
    long address = (long) 0x1000;
    boolean decRefCount = false;
    int rc = 1;

    List<RefCountChangeInfo> list = null;

    rchi.refCountChanged(address, decRefCount, rc);
    list = rchi.peekRefCountInfo(address);
    assertEquals(1, list.size());
    RefCountChangeInfo rcci = list.get(0);
    assertEquals(0, rcci.getUseCount()); // line 258 of ref cnt helper does not set useCount = 1
                                         // when adding new entry?

    rchi.refCountChanged(address, decRefCount, rc);
    list = rchi.peekRefCountInfo(address);
    assertEquals(1, list.size());
    rcci = list.get(0);
    assertEquals(1, rcci.getUseCount()); // line 258 of ref cnt helper does not set useCount = 1
                                         // when adding new entry?

    List<RefCountChangeInfo> info = rchi.getRefCountInfo(address); // now getRefCountInfo
    assertEquals(1, info.size());
    rcci = info.get(0);
    assertEquals(1, rcci.getUseCount());

    list = rchi.peekRefCountInfo(address);
    assertEquals(0, list.size()); // getRefCountInfo leaves list LOCKED (i.e. empty)
  }

  private ReferenceCountHelperImpl getTrueTrue() {
    return new ReferenceCountHelperImpl(true, true);
  }

  private ReferenceCountHelperImpl getTrueFalse() {
    return new ReferenceCountHelperImpl(true, false);
  }

  private ReferenceCountHelperImpl getFalseTrue() {
    return new ReferenceCountHelperImpl(false, true);
  }

  private ReferenceCountHelperImpl getFalseFalse() {
    return new ReferenceCountHelperImpl(false, false);
  }

  private ReferenceCountHelperImpl getHookedImpl() {
    return new HookedReferenceCountHelperImpl(true, true);
  }

  @Test
  public void doGetRefCountInfoNonRegionEntryConcurrencyTest() {
    rchi = getHookedImpl();
    long address = (long) 0x1000;
    int rc = 1;
    RefCountChangeInfo rcci;

    List<RefCountChangeInfo> list = null;

    rchi.setReferenceCountOwner("TestOwner"); // assume test identity

    rchi.refCountChanged(address, false, rc);
    list = rchi.peekRefCountInfo(address);
    assertEquals(1, list.size());
    rcci = list.get(0);
    assertEquals(1, rcci.getUseCount()); // hooked impl simulates a concurrent update, so cnt is >
                                         // expected

    rchi.setReferenceCountOwner(null); // sets owner to null and resets count
    rchi.setReferenceCountOwner(null); // sets owner to null and resets count

    rchi.setReferenceCountOwner("TestOwner2"); // assume new identity

    rchi.refCountChanged(address, false, rc);
    list = rchi.peekRefCountInfo(address);
    assertEquals(1, list.size());
    rcci = list.get(0);
    assertEquals(2, rcci.getUseCount()); // list is not null, so hook not used

    rchi.refCountChanged(address, true, rc); // dec ref count
    list = rchi.peekRefCountInfo(address);
    assertEquals(2, list.size()); // dec adds new list of stack traces
    rcci = list.get(1);
    assertEquals(0, rcci.getUseCount()); // cnt starts at 0 for new entries

    List<RefCountChangeInfo> info = rchi.getRefCountInfo(address); // now getRefCountInfo
    assertEquals(3, info.size()); // hooked impl added one to list
    rcci = info.get(2);
    assertEquals(0, rcci.getUseCount()); // count starts at 0 for new entries

    list = rchi.peekRefCountInfo(address);
    assertEquals(0, list.size()); // getRefCountInfo leaves list LOCKED (i.e. empty)
  }

  @Test
  public void doGetRefCountInfoRegionEntryConcurrencyTest() {
    rchi = getHookedImpl();
    long address = (long) 0x1000;
    int rc = 1;
    RefCountChangeInfo rcci;

    List<RefCountChangeInfo> list = null;

    RegionEntry re = mock(RegionEntry.class);
    rchi.setReferenceCountOwner(re); // set owner to region entry type

    rchi.refCountChanged(address, false, rc);
    list = rchi.peekRefCountInfo(address);
    assertEquals(1, list.size());
    rcci = list.get(0);
    assertEquals(1, rcci.getUseCount()); // hooked impl simulates a concurrent update, so cnt is >
                                         // expected

    rchi.setReferenceCountOwner(null); // sets owner to null and resets count
    rchi.setReferenceCountOwner(null); // sets owner to null and resets count

    RegionEntry re2 = mock(RegionEntry.class);
    rchi.setReferenceCountOwner(re2); // set owner to region entry type

    rchi.refCountChanged(address, false, rc);
    list = rchi.peekRefCountInfo(address);
    assertEquals(1, list.size());
    rcci = list.get(0);
    assertEquals(2, rcci.getUseCount()); // list is not null, so hook not used

    rchi.refCountChanged(address, true, rc); // dec ref count
    list = rchi.peekRefCountInfo(address);
    assertEquals(2, list.size()); // dec adds new list of stack traces
    rcci = list.get(1);
    assertEquals(0, rcci.getUseCount()); // cnt starts at 0 for new entries

    List<RefCountChangeInfo> info = rchi.getRefCountInfo(address); // now getRefCountInfo
    assertEquals(3, info.size()); // hooked impl added one to list
    rcci = info.get(2);
    assertEquals(0, rcci.getUseCount()); // count starts at 0 for new entries

    list = rchi.peekRefCountInfo(address);
    assertEquals(0, list.size()); // getRefCountInfo leaves list LOCKED (i.e. empty)
  }

  private class HookedReferenceCountHelperImpl extends ReferenceCountHelperImpl {
    HookedReferenceCountHelperImpl(boolean trackRefCounts, boolean trackFreedRefCounts) {
      super(trackRefCounts, trackFreedRefCounts);
    }

    protected int refCountChangedTestHookCount = 0;

    /*
     * Update list of stack traces for address. Hooked SUT should see that the list changed.
     */
    @Override
    protected void getReferenceCountInfoTestHook(
        ConcurrentMap<Long, List<RefCountChangeInfo>> stacktraces, long address) {
      List<RefCountChangeInfo> updatedList =
          new ArrayList<RefCountChangeInfo>(stacktraces.get(address));
      RefCountChangeInfo rcci = new RefCountChangeInfo(false, 0, "TestOwner");
      updatedList.add(rcci);
      stacktraces.put(address, updatedList);
    }

    /*
     * Reinvoke refCountChanged to update reference count. Hooked SUT should see that the count has
     * changed.
     */
    @Override
    protected void refCountChangedTestHook(Long address, boolean decRefCount, int rc) {
      if (refCountChangedTestHookCount == 0) {
        refCountChangedTestHookCount++;
        refCountChanged(address, decRefCount, rc);
      } else {
        refCountChangedTestHookCount--;
      }
    }
  }

}
