/*MUST be called under the WriteLock*/
    @NotNull
    private Map<Integer, SerializedStubTree> readOldData(final int key) throws StorageException {
      final Map<Integer, SerializedStubTree> result = new HashMap<Integer, SerializedStubTree>();

      IndexStorage<Integer, SerializedStubTree> indexStorage = myStorage;
      if (indexStorage instanceof MemoryIndexStorage) {
        final MemoryIndexStorage<Integer, SerializedStubTree> memIndexStorage =
            (MemoryIndexStorage<Integer, SerializedStubTree>) indexStorage;
        if (!memIndexStorage.isBufferingEnabled()) {
          // if buffering is not enabled, use backend storage to make sure
          // the returned stub tree contains no data corresponding to unsaved documents.
          // This will ensure that correct set of old keys is used for update
          indexStorage = memIndexStorage.getBackendStorage();
        }
      }
      try {
        final ValueContainer<SerializedStubTree> valueContainer = indexStorage.read(key);
        if (valueContainer.size() != 1) {
          LOG.assertTrue(valueContainer.size() == 0);
          return result;
        }

        result.put(key, valueContainer.getValueIterator().next());
        return result;
      } catch (RuntimeException e) {
        final Throwable cause = e.getCause();
        if (cause instanceof IOException) {
          throw new StorageException(cause);
        }
        throw e;
      }
    }
  // need 'synchronized' to ensure atomic initialization of merged data
  // because several threads that acquired read lock may simultaneously execute the method
  private ValueContainerImpl<Value> getMergedData() {
    ValueContainerImpl<Value> merged = myMerged;
    if (merged != null) {
      return merged;
    }
    synchronized (myInitializer.getLock()) {
      merged = myMerged;
      if (merged != null) {
        return merged;
      }

      final ValueContainer<Value> fromDisk = myInitializer.compute();
      final ValueContainerImpl<Value> newMerged;

      if (fromDisk instanceof ValueContainerImpl) {
        newMerged = ((ValueContainerImpl<Value>) fromDisk).copy();
      } else {
        newMerged = ((ChangeTrackingValueContainer<Value>) fromDisk).getMergedData().copy();
      }

      TIntHashSet invalidated = myInvalidated;
      if (invalidated != null) {
        invalidated.forEach(
            new TIntProcedure() {
              @Override
              public boolean execute(int inputId) {
                newMerged.removeAssociatedValue(inputId);
                return true;
              }
            });
      }

      ValueContainerImpl<Value> added = myAdded;
      if (added != null) {
        added.forEach(
            new ContainerAction<Value>() {
              @Override
              public boolean perform(final int id, final Value value) {
                newMerged.removeAssociatedValue(
                    id); // enforcing "one-value-per-file for particular key" invariant
                newMerged.addValue(id, value);
                return true;
              }
            });
      }
      setNeedsCompacting(fromDisk.needsCompacting());

      myMerged = newMerged;
      return newMerged;
    }
  }
    private void saveImpl(
        final DataOutput out, final ValueContainer<T> container, final boolean asRemovedData)
        throws IOException {
      DataInputOutputUtil.writeSINT(out, container.size());
      for (final Iterator<T> valueIterator = container.getValueIterator();
          valueIterator.hasNext(); ) {
        final T value = valueIterator.next();
        myExternalizer.save(out, value);

        final ValueContainer.IntIterator ids = container.getInputIdsIterator(value);
        if (ids != null) {
          DataInputOutputUtil.writeSINT(out, ids.size());
          while (ids.hasNext()) {
            final int id = ids.next();
            DataInputOutputUtil.writeSINT(out, asRemovedData ? -id : id);
          }
        } else {
          DataInputOutputUtil.writeSINT(out, 0);
        }
      }
    }
  @Override
  protected void doPut(Key key, ValueContainer<Value> container) throws IOException {
    synchronized (myEnumerator) {
      ChangeTrackingValueContainer<Value> valueContainer =
          (ChangeTrackingValueContainer<Value>) container;
      if (!valueContainer.needsCompacting()) {
        final BufferExposingByteArrayOutputStream bytes = new BufferExposingByteArrayOutputStream();
        //noinspection IOResourceOpenedButNotSafelyClosed
        final DataOutputStream _out = new DataOutputStream(bytes);
        final TIntHashSet set = valueContainer.getInvalidated();
        if (set.size() > 0) {
          for (int inputId : set.toArray()) {
            ValueContainerExternalizer.saveInvalidateCommand(_out, inputId);
          }
        }
        final ValueContainer<Value> toRemove = valueContainer.getRemovedDelta();
        if (toRemove.size() > 0) {
          myValueContainerExternalizer.saveAsRemoved(_out, toRemove);
        }

        final ValueContainer<Value> toAppend = valueContainer.getAddedDelta();
        if (toAppend.size() > 0) {
          myValueContainerExternalizer.save(_out, toAppend);
        }

        appendData(
            key,
            new PersistentHashMap.ValueDataAppender() {
              public void append(final DataOutput out) throws IOException {
                out.write(bytes.getInternalBuffer(), 0, bytes.size());
              }
            });
      } else {
        // rewrite the value container for defragmentation
        super.doPut(key, valueContainer);
      }
    }
  }
  @Override
  public <Key, Psi extends PsiElement> Collection<Psi> get(
      @NotNull final StubIndexKey<Key, Psi> indexKey,
      @NotNull final Key key,
      final Project project,
      final GlobalSearchScope scope) {
    FileBasedIndex.getInstance().ensureUpToDate(StubUpdatingIndex.INDEX_ID, project, scope);

    final PersistentFS fs = (PersistentFS) ManagingFS.getInstance();
    final PsiManager psiManager = PsiManager.getInstance(project);

    final List<Psi> result = new ArrayList<Psi>();
    final MyIndex<Key> index = (MyIndex<Key>) myIndices.get(indexKey);

    try {
      try {
        // disable up-to-date check to avoid locks on attempt to acquire index write lock while
        // holding at the same time the readLock for this index
        FileBasedIndex.disableUpToDateCheckForCurrentThread();
        index.getReadLock().lock();
        final ValueContainer<TIntArrayList> container = index.getData(key);

        container.forEach(
            new ValueContainer.ContainerAction<TIntArrayList>() {
              @Override
              public void perform(final int id, final TIntArrayList value) {
                final VirtualFile file = IndexInfrastructure.findFileByIdIfCached(fs, id);
                if (file != null && (scope == null || scope.contains(file))) {
                  StubTree stubTree = null;

                  final PsiFile _psifile = psiManager.findFile(file);
                  PsiFileWithStubSupport psiFile = null;

                  if (_psifile != null && !(_psifile instanceof PsiPlainTextFile)) {
                    if (_psifile instanceof PsiFileWithStubSupport) {
                      psiFile = (PsiFileWithStubSupport) _psifile;
                      stubTree = psiFile.getStubTree();
                      if (stubTree == null && psiFile instanceof PsiFileImpl) {
                        stubTree = ((PsiFileImpl) psiFile).calcStubTree();
                      }
                    }
                  }

                  if (stubTree != null || psiFile != null) {
                    if (stubTree == null) {
                      stubTree = StubTreeLoader.getInstance().readFromVFile(project, file);
                      if (stubTree != null) {
                        final List<StubElement<?>> plained = stubTree.getPlainList();
                        for (int i = 0; i < value.size(); i++) {
                          final StubElement<?> stub = plained.get(value.get(i));
                          final ASTNode tree = psiFile.findTreeForStub(stubTree, stub);

                          if (tree != null) {
                            if (tree.getElementType() == stubType(stub)) {
                              result.add((Psi) tree.getPsi());
                            } else {
                              String persistedStubTree =
                                  ((PsiFileStubImpl) stubTree.getRoot()).printTree();

                              String stubTreeJustBuilt =
                                  ((PsiFileStubImpl)
                                          ((IStubFileElementType)
                                                  ((PsiFileImpl) psiFile).getContentElementType())
                                              .getBuilder()
                                              .buildStubTree(psiFile))
                                      .printTree();

                              StringBuilder builder = new StringBuilder();
                              builder.append("Oops\n");

                              builder.append("Recorded stub:-----------------------------------\n");
                              builder.append(persistedStubTree);
                              builder.append(
                                  "\nAST built stub: ------------------------------------\n");
                              builder.append(stubTreeJustBuilt);
                              builder.append("\n");
                              LOG.info(builder.toString());

                              // requestReindex() may want to acquire write lock (for indices not
                              // requiring content loading)
                              // thus, because here we are under read lock, need to use invoke later
                              ApplicationManager.getApplication()
                                  .invokeLater(
                                      new Runnable() {
                                        @Override
                                        public void run() {
                                          FileBasedIndex.getInstance().requestReindex(file);
                                        }
                                      },
                                      ModalityState.NON_MODAL);
                            }
                          }
                        }
                      }
                    } else {
                      final List<StubElement<?>> plained = stubTree.getPlainList();
                      for (int i = 0; i < value.size(); i++) {
                        result.add((Psi) plained.get(value.get(i)).getPsi());
                      }
                    }
                  }
                }
              }
            });
      } finally {
        index.getReadLock().unlock();
        FileBasedIndex.enableUpToDateCheckForCurrentThread();
      }
    } catch (StorageException e) {
      forceRebuild(e);
    } catch (RuntimeException e) {
      final Throwable cause = FileBasedIndex.getCauseToRebuildIndex(e);
      if (cause != null) {
        forceRebuild(cause);
      } else {
        throw e;
      }
    }

    return result;
  }