public class MemoryIndexStoreImplTest extends EngineTestCase {
  /**
   * {@link Location} has no "equals" and "hasCode", so to compare locations by value we need to
   * wrap them into such object.
   */
  private static class LocationEqualsWrapper {
    private final Location location;

    LocationEqualsWrapper(Location location) {
      this.location = location;
    }

    @Override
    public boolean equals(Object obj) {
      if (!(obj instanceof LocationEqualsWrapper)) {
        return false;
      }
      LocationEqualsWrapper other = (LocationEqualsWrapper) obj;
      return other.location.getOffset() == other.location.getOffset()
          && other.location.getLength() == other.location.getLength()
          && Objects.equal(other.location.getElement(), location.getElement());
    }

    @Override
    public int hashCode() {
      return Objects.hashCode(location.getElement(), location.getOffset(), location.getLength());
    }
  }

  /** Asserts that the "actual" locations have all the "expected" locations and only them. */
  private static void assertLocations(Location[] actual, Location... expected) {
    LocationEqualsWrapper[] actualWrappers = wrapLocations(actual);
    LocationEqualsWrapper[] expectedWrappers = wrapLocations(expected);
    assertThat(actualWrappers).containsOnly((Object[]) expectedWrappers);
  }

  /** @return the new {@link Location} mock. */
  private static Location mockLocation(Element element) {
    Location location = mock(Location.class);
    when(location.clone()).thenReturn(location);
    when(location.getElement()).thenReturn(element);
    return location;
  }

  /** @return the {@link SourceContainer} mock with contains given {@link Source}s. */
  private static SourceContainer mockSourceContainer(Source... sources) {
    final Set<Source> sourceSet = ImmutableSet.<Source>builder().add(sources).build();
    SourceContainer container = mock(SourceContainer.class);
    when(container.contains(any(Source.class)))
        .then(
            new Answer<Boolean>() {
              @Override
              public Boolean answer(InvocationOnMock invocation) throws Throwable {
                return sourceSet.contains(invocation.getArguments()[0]);
              }
            });
    return container;
  }

  /** Wraps the given locations into {@link LocationEqualsWrapper}. */
  private static LocationEqualsWrapper[] wrapLocations(Location[] locations) {
    List<LocationEqualsWrapper> wrappers = Lists.newArrayList();
    for (Location location : locations) {
      wrappers.add(new LocationEqualsWrapper(location));
    }
    return wrappers.toArray(new LocationEqualsWrapper[wrappers.size()]);
  }

  private MemoryIndexStoreImpl store = new MemoryIndexStoreImpl();
  private AnalysisContext contextA = mock(AnalysisContext.class);
  private AnalysisContext contextB = mock(AnalysisContext.class);
  private AnalysisContext contextC = mock(AnalysisContext.class);
  private ElementLocation elementLocationA = new ElementLocationImpl("elementLocationA");
  private ElementLocation elementLocationB = new ElementLocationImpl("elementLocationB");
  private ElementLocation elementLocationC = new ElementLocationImpl("elementLocationC");
  private ElementLocation elementLocationD = new ElementLocationImpl("elementLocationD");
  private Element elementA = mock(Element.class);
  private Element elementB = mock(Element.class);
  private Element elementC = mock(Element.class);
  private Element elementD = mock(Element.class);
  private Source librarySource = mock(Source.class);
  private Source sourceA = mock(Source.class);
  private Source sourceB = mock(Source.class);
  private Source sourceC = mock(Source.class);
  private Source sourceD = mock(Source.class);
  private LibraryElement libraryElement = mock(LibraryElement.class);
  private CompilationUnitElement libraryUnitElement = mock(CompilationUnitElement.class);
  private CompilationUnitElement unitElementA = mock(CompilationUnitElement.class);
  private CompilationUnitElement unitElementB = mock(CompilationUnitElement.class);
  private CompilationUnitElement unitElementC = mock(CompilationUnitElement.class);
  private CompilationUnitElement unitElementD = mock(CompilationUnitElement.class);
  private Relationship relationship = Relationship.getRelationship("test-relationship");
  private Location location = mockLocation(elementC);

  public void test_aboutToIndex_removedContext() throws Exception {
    store.removeContext(contextA);
    boolean mayIndex = store.aboutToIndex(contextA, unitElementA);
    assertFalse(mayIndex);
  }

  public void test_aboutToIndex_sharedSource_inTwoLibraries() throws Exception {
    Source librarySourceA = mock(Source.class);
    Source librarySourceB = mock(Source.class);
    LibraryElement libraryA = mock(LibraryElement.class);
    LibraryElement libraryB = mock(LibraryElement.class);
    CompilationUnitElement libraryAUnit = mock(CompilationUnitElement.class);
    CompilationUnitElement libraryBUnit = mock(CompilationUnitElement.class);
    when(libraryA.getDefiningCompilationUnit()).thenReturn(libraryAUnit);
    when(libraryB.getDefiningCompilationUnit()).thenReturn(libraryBUnit);
    when(libraryA.getSource()).thenReturn(librarySourceA);
    when(libraryB.getSource()).thenReturn(librarySourceB);
    when(libraryAUnit.getSource()).thenReturn(librarySourceA);
    when(libraryBUnit.getSource()).thenReturn(librarySourceB);
    when(libraryAUnit.getLibrary()).thenReturn(libraryA);
    when(libraryBUnit.getLibrary()).thenReturn(libraryB);
    // build 2 units in different libraries
    CompilationUnitElement unitA = mock(CompilationUnitElement.class);
    CompilationUnitElement unitB = mock(CompilationUnitElement.class);
    when(unitA.getContext()).thenReturn(contextA);
    when(unitB.getContext()).thenReturn(contextA);
    when(unitA.getSource()).thenReturn(sourceA);
    when(unitB.getSource()).thenReturn(sourceA);
    when(unitA.getLibrary()).thenReturn(libraryA);
    when(unitB.getLibrary()).thenReturn(libraryB);
    when(libraryA.getParts()).thenReturn(new CompilationUnitElement[] {unitA});
    when(libraryB.getParts()).thenReturn(new CompilationUnitElement[] {unitB});
    // record relationships in both A and B
    Location locationA = mockLocation(unitA);
    Location locationB = mockLocation(unitB);
    store.aboutToIndex(contextA, libraryAUnit);
    store.aboutToIndex(contextA, libraryBUnit);
    store.aboutToIndex(contextA, unitA);
    store.aboutToIndex(contextA, unitB);
    store.recordRelationship(elementA, relationship, locationA);
    store.recordRelationship(elementA, relationship, locationB);
    {
      Location[] locations = store.getRelationships(elementA, relationship);
      assertLocations(locations, locationA, locationB);
    }
  }

  public void test_aboutToIndex_shouldRemoveSourceKeys() throws Exception {
    store.recordRelationship(elementA, relationship, location);
    // notify that we are going to re-index "A"
    store.aboutToIndex(contextA, unitElementA);
    // all keys in "A" should be removed (and we don't have any other sources)
    assertEquals(0, store.internalGetSourceKeyCount(contextA));
  }

  public void test_aboutToIndex_unitExcluded() throws Exception {
    // build library with defining unit
    Source librarySource = mock(Source.class);
    LibraryElement library = mock(LibraryElement.class);
    CompilationUnitElement libraryUnit = mock(CompilationUnitElement.class);
    when(library.getContext()).thenReturn(contextA);
    when(library.getDefiningCompilationUnit()).thenReturn(libraryUnit);
    when(library.getSource()).thenReturn(librarySource);
    when(libraryUnit.getContext()).thenReturn(contextA);
    when(libraryUnit.getSource()).thenReturn(librarySource);
    when(libraryUnit.getLibrary()).thenReturn(library);
    // build 2 library units
    CompilationUnitElement unitA = mock(CompilationUnitElement.class);
    CompilationUnitElement unitB = mock(CompilationUnitElement.class);
    when(unitA.getContext()).thenReturn(contextA);
    when(unitB.getContext()).thenReturn(contextA);
    when(unitA.getSource()).thenReturn(sourceA);
    when(unitB.getSource()).thenReturn(sourceB);
    when(unitA.getLibrary()).thenReturn(library);
    when(unitB.getLibrary()).thenReturn(library);
    // prepare locations
    Location locationA = mockLocation(unitA);
    Location locationB = mockLocation(unitB);
    // initially A and B in library
    when(library.getParts()).thenReturn(new CompilationUnitElement[] {unitA, unitB});
    store.aboutToIndex(contextA, libraryUnit);
    store.aboutToIndex(contextA, unitA);
    store.aboutToIndex(contextA, unitB);
    store.recordRelationship(elementA, relationship, locationA);
    store.recordRelationship(elementA, relationship, locationB);
    {
      Location[] locations = store.getRelationships(elementA, relationship);
      assertLocations(locations, locationA, locationB);
    }
    // exclude A from library
    when(library.getParts()).thenReturn(new CompilationUnitElement[] {unitA});
    boolean mayIndex = store.aboutToIndex(contextA, libraryUnit);
    assertTrue(mayIndex);
    {
      Location[] locations = store.getRelationships(elementA, relationship);
      assertLocations(locations, locationA);
    }
    // exclude B from library, empty now
    when(library.getParts()).thenReturn(new CompilationUnitElement[] {});
    store.aboutToIndex(contextA, libraryUnit);
    {
      Location[] locations = store.getRelationships(elementA, relationship);
      assertLocations(locations);
    }
  }

  public void test_clearSource_instrumented() throws Exception {
    Location locationB = mockLocation(elementB);
    Location locationC = mockLocation(elementC);
    // record: [B -> A] and [C -> A]
    {
      store.recordRelationship(elementA, relationship, locationB);
      store.recordRelationship(elementA, relationship, locationC);
      assertEquals(2, store.internalGetLocationCount());
      Location[] locations = store.getRelationships(elementA, relationship);
      assertLocations(locations, locationB, locationC);
    }
    // clear B, 1 relation and 1 location left
    InstrumentedAnalysisContextImpl iContextA = mock(InstrumentedAnalysisContextImpl.class);
    when(iContextA.getBasis()).thenReturn(contextA);
    store.aboutToIndex(iContextA, unitElementB);
    assertEquals(1, store.internalGetLocationCount());
    assertEquals(1, store.internalGetLocationCount(contextA));
    Location[] locations = store.getRelationships(elementA, relationship);
    assertLocations(locations, locationC);
  }

  public void test_getRelationships_hasOne() throws Exception {
    store.recordRelationship(elementA, relationship, location);
    Location[] locations = store.getRelationships(elementA, relationship);
    assertLocations(locations, location);
  }

  public void test_getRelationships_hasTwo() throws Exception {
    Location locationA = mockLocation(elementA);
    Location locationB = mockLocation(elementB);
    store.recordRelationship(elementA, relationship, locationA);
    store.recordRelationship(elementA, relationship, locationB);
    Location[] locations = store.getRelationships(elementA, relationship);
    assertLocations(locations, locationA, locationB);
  }

  public void test_getRelationships_noRelations() throws Exception {
    store.recordRelationship(elementA, relationship, location);
    Location[] locations =
        store.getRelationships(elementA, Relationship.getRelationship("no-such-relationship"));
    assertThat(locations).isEmpty();
  }

  public void test_getRelationships_twoContexts_oneSource() throws Exception {
    when(unitElementB.getSource()).thenReturn(sourceB);
    when(unitElementC.getSource()).thenReturn(sourceB);
    when(elementA.getContext()).thenReturn(contextA);
    when(elementB.getContext()).thenReturn(contextB);
    Location locationA = mockLocation(elementA);
    Location locationB = mockLocation(elementB);
    store.recordRelationship(elementA, relationship, locationA);
    store.recordRelationship(elementB, relationship, locationB);
    // "elementA"
    {
      Location[] locations = store.getRelationships(elementA, relationship);
      assertLocations(locations, locationA);
    }
    // "elementB"
    {
      Location[] locations = store.getRelationships(elementB, relationship);
      assertLocations(locations, locationB);
    }
  }

  public void test_getStatistics() throws Exception {
    // empty initially
    assertEquals("0 relationships in 0 keys in 0 sources", store.getStatistics());
    // record relationship
    store.recordRelationship(elementA, relationship, mockLocation(elementA));
    store.recordRelationship(elementA, relationship, mockLocation(elementB));
    store.recordRelationship(elementB, relationship, mockLocation(elementC));
    assertEquals("3 relationships in 2 keys in 2 sources", store.getStatistics());
  }

  public void test_recordRelationship() throws Exception {
    // no relationships initially
    assertEquals(0, store.internalGetLocationCount());
    // record relationship
    store.recordRelationship(elementA, relationship, location);
    assertEquals(1, store.internalGetLocationCount());
  }

  public void test_recordRelationship_member() throws Exception {
    Member member = mock(Member.class);
    when(member.getBaseElement()).thenReturn(elementA);
    // no relationships initially
    assertEquals(0, store.internalGetLocationCount());
    // record relationship
    store.recordRelationship(member, relationship, location);
    // no location for "member"
    {
      Location[] locations = store.getRelationships(member, relationship);
      assertLocations(locations);
    }
    // has location for "elementA"
    {
      Location[] locations = store.getRelationships(elementA, relationship);
      assertLocations(locations, location);
    }
  }

  public void test_recordRelationship_noElement() throws Exception {
    store.recordRelationship(null, relationship, location);
    assertEquals(0, store.internalGetLocationCount());
  }

  public void test_recordRelationship_noElementContext() throws Exception {
    when(elementA.getContext()).thenReturn(null);
    store.recordRelationship(elementA, relationship, location);
    assertEquals(0, store.internalGetLocationCount());
  }

  public void test_recordRelationship_noElementSource() throws Exception {
    when(elementA.getSource()).thenReturn(null);
    store.recordRelationship(elementA, relationship, location);
    assertEquals(0, store.internalGetLocationCount());
  }

  public void test_recordRelationship_noLocation() throws Exception {
    store.recordRelationship(elementA, relationship, null);
    assertEquals(0, store.internalGetLocationCount());
  }

  public void test_recordRelationship_noLocationContext() throws Exception {
    when(location.getElement().getContext()).thenReturn(null);
    store.recordRelationship(elementA, relationship, location);
    assertEquals(0, store.internalGetLocationCount());
  }

  public void test_recordRelationship_noLocationSource() throws Exception {
    when(location.getElement().getSource()).thenReturn(null);
    store.recordRelationship(elementA, relationship, location);
    assertEquals(0, store.internalGetLocationCount());
  }

  public void test_removeContext_instrumented() throws Exception {
    InstrumentedAnalysisContextImpl instrumentedContext =
        mock(InstrumentedAnalysisContextImpl.class);
    when(instrumentedContext.getBasis()).thenReturn(contextA);
    // configure B
    when(elementB.getContext()).thenReturn(contextA);
    Location locationB = mockLocation(elementB);
    // record: [B -> A]
    {
      store.recordRelationship(elementA, relationship, locationB);
      assertEquals(1, store.internalGetLocationCount());
      assertEquals(1, store.internalGetKeyCount());
    }
    // remove _wrapper_ of context A
    InstrumentedAnalysisContextImpl iContextA = mock(InstrumentedAnalysisContextImpl.class);
    when(iContextA.getBasis()).thenReturn(contextA);
    store.removeContext(iContextA);
    assertEquals(0, store.internalGetLocationCount());
    assertEquals(0, store.internalGetKeyCount());
  }

  public void test_removeContext_null() throws Exception {
    store.removeContext(null);
  }

  public void test_removeContext_withDeclaration() throws Exception {
    when(elementB.getContext()).thenReturn(contextB);
    when(elementC.getContext()).thenReturn(contextC);
    // configure B and C
    Location locationB = mockLocation(elementB);
    Location locationC = mockLocation(elementC);
    // record: [B -> A] and [C -> A]
    {
      store.recordRelationship(elementA, relationship, locationB);
      store.recordRelationship(elementA, relationship, locationC);
      assertEquals(2, store.internalGetLocationCount());
      assertEquals(1, store.internalGetKeyCount());
      assertEquals(0, store.internalGetLocationCount(contextA));
      assertEquals(1, store.internalGetLocationCount(contextB));
      assertEquals(1, store.internalGetLocationCount(contextC));
      // we get locations from all contexts
      Location[] locations = store.getRelationships(elementA, relationship);
      assertLocations(locations, locationB, locationC);
    }
    // remove A, so no relations anymore
    // remove B, 1 relation and 1 location left
    store.removeContext(contextA);
    assertEquals(0, store.internalGetLocationCount());
    assertEquals(0, store.internalGetLocationCount(contextA));
    assertEquals(0, store.internalGetLocationCount(contextB));
    assertEquals(0, store.internalGetLocationCount(contextC));
    {
      Location[] locations = store.getRelationships(elementA, relationship);
      assertThat(locations).isEmpty();
    }
  }

  public void test_removeContext_withRelationship() throws Exception {
    when(elementB.getContext()).thenReturn(contextB);
    when(elementC.getContext()).thenReturn(contextC);
    // configure B and C
    Location locationB = mockLocation(elementB);
    Location locationC = mockLocation(elementC);
    // record: [B -> A] and [C -> A]
    {
      store.aboutToIndex(contextB, unitElementB);
      store.aboutToIndex(contextC, unitElementC);
      store.recordRelationship(elementA, relationship, locationB);
      store.recordRelationship(elementA, relationship, locationC);
      assertEquals(2, store.internalGetLocationCount());
      assertEquals(0, store.internalGetLocationCount(contextA));
      assertEquals(1, store.internalGetLocationCount(contextB));
      assertEquals(1, store.internalGetLocationCount(contextC));
      // we get locations from all contexts
      Location[] locations = store.getRelationships(elementA, relationship);
      assertLocations(locations, locationB, locationC);
    }
    // remove B, 1 relation and 1 location left
    store.removeContext(contextB);
    assertEquals(1, store.internalGetLocationCount());
    assertEquals(0, store.internalGetLocationCount(contextA));
    assertEquals(0, store.internalGetLocationCount(contextB));
    assertEquals(1, store.internalGetLocationCount(contextC));
    {
      Location[] locations = store.getRelationships(elementA, relationship);
      assertLocations(locations, locationC);
    }
    // now remove C, empty
    store.removeContext(contextC);
    assertEquals(0, store.internalGetLocationCount(contextA));
    assertEquals(0, store.internalGetLocationCount(contextB));
    assertEquals(0, store.internalGetLocationCount(contextC));
    {
      Location[] locations = store.getRelationships(elementA, relationship);
      assertThat(locations).isEmpty();
    }
  }

  public void test_removeSource_null() throws Exception {
    store.removeSource(null, null);
  }

  public void test_removeSource_withDeclaration() throws Exception {
    Location locationB = mockLocation(elementB);
    Location locationC = mockLocation(elementC);
    // record: [B -> A] and [C -> A]
    {
      store.recordRelationship(elementA, relationship, locationB);
      store.recordRelationship(elementA, relationship, locationC);
      assertEquals(2, store.internalGetLocationCount());
      Location[] locations = store.getRelationships(elementA, relationship);
      assertLocations(locations, locationB, locationC);
    }
    // remove A, no relations
    store.removeSource(contextA, sourceA);
    assertEquals(0, store.internalGetLocationCount());
  }

  public void test_removeSource_withRelationship() throws Exception {
    Location locationB = mockLocation(elementB);
    Location locationC = mockLocation(elementC);
    // record: [B -> A] and [C -> A]
    {
      store.recordRelationship(elementA, relationship, locationB);
      store.recordRelationship(elementA, relationship, locationC);
      assertEquals(2, store.internalGetLocationCount());
      Location[] locations = store.getRelationships(elementA, relationship);
      assertLocations(locations, locationB, locationC);
    }
    // remove B, 1 relation and 1 location left
    store.removeSource(contextA, sourceB);
    assertEquals(1, store.internalGetLocationCount());
    assertEquals(1, store.internalGetLocationCount(contextA));
    Location[] locations = store.getRelationships(elementA, relationship);
    assertLocations(locations, locationC);
  }

  public void test_removeSource_withRelationship_instrumented() throws Exception {
    Location locationB = mockLocation(elementB);
    Location locationC = mockLocation(elementC);
    // record: [B -> A] and [C -> A]
    {
      store.recordRelationship(elementA, relationship, locationB);
      store.recordRelationship(elementA, relationship, locationC);
      assertEquals(2, store.internalGetLocationCount());
      Location[] locations = store.getRelationships(elementA, relationship);
      assertLocations(locations, locationB, locationC);
    }
    // remove B, 1 relation and 1 location left
    InstrumentedAnalysisContextImpl iContextA = mock(InstrumentedAnalysisContextImpl.class);
    when(iContextA.getBasis()).thenReturn(contextA);
    store.removeSource(iContextA, sourceB);
    assertEquals(1, store.internalGetLocationCount());
    assertEquals(1, store.internalGetLocationCount(contextA));
    Location[] locations = store.getRelationships(elementA, relationship);
    assertLocations(locations, locationC);
  }

  public void test_removeSource_withRelationship_twoContexts_oneSource() throws Exception {
    when(elementB.getContext()).thenReturn(contextB);
    when(elementC.getContext()).thenReturn(contextC);
    when(elementC.getSource()).thenReturn(sourceB);
    when(unitElementC.getSource()).thenReturn(sourceB);
    // configure B and C
    Location locationB = mockLocation(elementB);
    Location locationC = mockLocation(elementC);
    // record: [B -> A] and [C -> A]
    {
      store.aboutToIndex(contextB, unitElementB);
      store.aboutToIndex(contextC, unitElementC);
      store.recordRelationship(elementA, relationship, locationB);
      store.recordRelationship(elementA, relationship, locationC);
      assertEquals(2, store.internalGetLocationCount());
      assertEquals(1, store.internalGetLocationCount(contextB));
      assertEquals(1, store.internalGetLocationCount(contextC));
      // we get locations from all contexts
      Location[] locations = store.getRelationships(elementA, relationship);
      assertLocations(locations, locationB, locationC);
    }
    // remove "B" in B, 1 relation and 1 location left
    store.removeSource(contextB, sourceB);
    assertEquals(1, store.internalGetLocationCount());
    assertEquals(0, store.internalGetLocationCount(contextB));
    assertEquals(1, store.internalGetLocationCount(contextC));
    {
      Location[] locations = store.getRelationships(elementA, relationship);
      assertLocations(locations, locationC);
    }
    // now remove "B" in C, empty
    store.removeSource(contextC, sourceB);
    assertEquals(0, store.internalGetLocationCount());
    assertEquals(0, store.internalGetLocationCount(contextB));
    assertEquals(0, store.internalGetLocationCount(contextC));
    {
      Location[] locations = store.getRelationships(elementA, relationship);
      assertThat(locations).isEmpty();
    }
  }

  public void test_removeSources_nullContext() throws Exception {
    // record
    {
      store.recordRelationship(IndexConstants.UNIVERSE, relationship, location);
      assertEquals(1, store.internalGetLocationCount());
    }
    // remove "null" context, should never happen - ignored
    SourceContainer sourceContainer = new DirectoryBasedSourceContainer("/path/");
    store.removeSources(null, sourceContainer);
    assertEquals(1, store.internalGetLocationCount());
  }

  public void test_removeSources_withDeclaration() throws Exception {
    Location locationB = mockLocation(elementB);
    Location locationC = mockLocation(elementC);
    // record: A, [B -> A],  [C -> A] and [B -> C]
    {
      store.recordRelationship(elementA, relationship, locationB);
      store.recordRelationship(elementA, relationship, locationC);
      store.recordRelationship(elementC, relationship, locationB);
      assertEquals(3, store.internalGetLocationCount());
      Location[] locations = store.getRelationships(elementA, relationship);
      assertLocations(locations, locationB, locationC);
    }
    // remove container with [A], only [B -> C] left
    SourceContainer containerA = mockSourceContainer(sourceA);
    store.removeSources(contextA, containerA);
    assertEquals(1, store.internalGetLocationCount());
    assertEquals(1, store.internalGetLocationCount(contextA));
    {
      Location[] locations = store.getRelationships(elementC, relationship);
      assertLocations(locations, locationB);
    }
  }

  public void test_removeSources_withRelationship() throws Exception {
    Location locationB = mockLocation(elementB);
    Location locationC = mockLocation(elementC);
    // record: [B -> A] and [C -> A]
    {
      store.recordRelationship(elementA, relationship, locationB);
      store.recordRelationship(elementA, relationship, locationC);
      assertEquals(2, store.internalGetLocationCount());
      Location[] locations = store.getRelationships(elementA, relationship);
      assertLocations(locations, locationB, locationC);
    }
    // remove container with [B], 1 relation and 1 location left
    SourceContainer containerB = mockSourceContainer(sourceB);
    store.removeSources(contextA, containerB);
    assertEquals(1, store.internalGetLocationCount());
    assertEquals(1, store.internalGetLocationCount(contextA));
    Location[] locations = store.getRelationships(elementA, relationship);
    assertLocations(locations, locationC);
  }

  public void test_tryToRecord_afterContextRemove_element() throws Exception {
    Location locationB = mockLocation(elementB);
    // remove "A" - context of "elementA"
    store.removeContext(contextA);
    // so, this record request is ignored
    store.recordRelationship(elementA, relationship, locationB);
    assertEquals(0, store.internalGetLocationCount());
  }

  public void test_tryToRecord_afterContextRemove_location() throws Exception {
    Location locationB = mockLocation(elementB);
    when(elementB.getContext()).thenReturn(contextB);
    // remove "B" - context of location
    store.removeContext(contextB);
    // so, this record request is ignored
    store.recordRelationship(elementA, relationship, locationB);
    assertEquals(0, store.internalGetLocationCount());
  }

  public void test_writeRead() throws Exception {
    when(contextA.getElement(eq(elementLocationA))).thenReturn(elementA);
    when(contextB.getElement(eq(elementLocationB))).thenReturn(elementB);
    when(elementA.getContext()).thenReturn(contextA);
    when(elementB.getContext()).thenReturn(contextB);
    // fill store
    Location locationA = new Location(elementA, 0, 0);
    Location locationB = new Location(elementB, 0, 0);
    store.aboutToIndex(contextA, unitElementA);
    store.aboutToIndex(contextB, unitElementB);
    store.recordRelationship(elementA, relationship, locationA);
    store.recordRelationship(elementB, relationship, locationB);
    assertEquals(2, store.internalGetKeyCount());
    assertEquals(2, store.internalGetLocationCount());
    assertLocations(store.getRelationships(elementA, relationship), locationA);
    assertLocations(store.getRelationships(elementB, relationship), locationB);
    // write
    byte[] content;
    {
      ByteArrayOutputStream baos = new ByteArrayOutputStream();
      store.writeIndex(contextA, baos);
      content = baos.toByteArray();
    }
    // clear
    store.removeContext(contextA);
    store.removeContext(contextB);
    assertEquals(0, store.internalGetKeyCount());
    assertEquals(0, store.internalGetLocationCount());
    // we need to re-create AnalysisContext, current instance was marked as removed
    {
      contextA = mock(AnalysisContext.class);
      when(contextA.getElement(eq(elementLocationA))).thenReturn(elementA);
      when(elementA.getContext()).thenReturn(contextA);
    }
    // read
    {
      ByteArrayInputStream bais = new ByteArrayInputStream(content);
      store.readIndex(contextA, bais);
    }
    // validate after read
    assertEquals(1, store.internalGetKeyCount());
    assertEquals(1, store.internalGetLocationCount());
    {
      Location[] locations = store.getRelationships(elementA, relationship);
      assertLocations(locations, locationA);
    }
  }

  public void test_writeRead_invalidVersion() throws Exception {
    // write fake content with invalid version
    byte[] content;
    {
      ByteArrayOutputStream baos = new ByteArrayOutputStream();
      DataOutputStream dos = new DataOutputStream(baos);
      dos.writeInt(-1);
      content = baos.toByteArray();
    }
    // read
    try {
      ByteArrayInputStream bais = new ByteArrayInputStream(content);
      store.readIndex(contextA, bais);
      fail();
    } catch (IOException e) {
    }
  }

  @Override
  protected void setUp() throws Exception {
    super.setUp();
    when(contextA.toString()).thenReturn("contextA");
    when(contextB.toString()).thenReturn("contextB");
    when(contextC.toString()).thenReturn("contextC");
    when(sourceA.toString()).thenReturn("sourceA");
    when(sourceB.toString()).thenReturn("sourceB");
    when(sourceC.toString()).thenReturn("sourceC");
    when(sourceD.toString()).thenReturn("sourceD");
    when(elementA.toString()).thenReturn("elementA");
    when(elementB.toString()).thenReturn("elementB");
    when(elementC.toString()).thenReturn("elementC");
    when(elementD.toString()).thenReturn("elementD");
    when(elementA.getContext()).thenReturn(contextA);
    when(elementB.getContext()).thenReturn(contextA);
    when(elementC.getContext()).thenReturn(contextA);
    when(elementD.getContext()).thenReturn(contextA);
    when(elementA.getLocation()).thenReturn(elementLocationA);
    when(elementB.getLocation()).thenReturn(elementLocationB);
    when(elementC.getLocation()).thenReturn(elementLocationC);
    when(elementD.getLocation()).thenReturn(elementLocationD);
    when(elementA.getEnclosingElement()).thenReturn(unitElementA);
    when(elementB.getEnclosingElement()).thenReturn(unitElementB);
    when(elementC.getEnclosingElement()).thenReturn(unitElementC);
    when(elementD.getEnclosingElement()).thenReturn(unitElementD);
    when(elementA.getSource()).thenReturn(sourceA);
    when(elementB.getSource()).thenReturn(sourceB);
    when(elementC.getSource()).thenReturn(sourceC);
    when(elementD.getSource()).thenReturn(sourceD);
    when(elementA.getLibrary()).thenReturn(libraryElement);
    when(elementB.getLibrary()).thenReturn(libraryElement);
    when(elementC.getLibrary()).thenReturn(libraryElement);
    when(elementD.getLibrary()).thenReturn(libraryElement);
    when(unitElementA.getSource()).thenReturn(sourceA);
    when(unitElementB.getSource()).thenReturn(sourceB);
    when(unitElementC.getSource()).thenReturn(sourceC);
    when(unitElementD.getSource()).thenReturn(sourceD);
    when(unitElementA.getLibrary()).thenReturn(libraryElement);
    when(unitElementB.getLibrary()).thenReturn(libraryElement);
    when(unitElementC.getLibrary()).thenReturn(libraryElement);
    when(unitElementD.getLibrary()).thenReturn(libraryElement);
    // library
    when(librarySource.toString()).thenReturn("libSource");
    when(libraryUnitElement.getSource()).thenReturn(librarySource);
    when(libraryElement.getSource()).thenReturn(librarySource);
    when(libraryElement.getDefiningCompilationUnit()).thenReturn(libraryUnitElement);
    // by default index all units
    store.aboutToIndex(contextA, unitElementA);
    store.aboutToIndex(contextA, unitElementB);
    store.aboutToIndex(contextA, unitElementC);
    store.aboutToIndex(contextA, unitElementD);
  }
}
 public void test_getRelationships_noRelations() throws Exception {
   store.recordRelationship(elementA, relationship, location);
   Location[] locations =
       store.getRelationships(elementA, Relationship.getRelationship("no-such-relationship"));
   assertThat(locations).isEmpty();
 }