public boolean differentiate(@NotNull Map<Integer, SerializedStubTree> newData,
                               @NotNull KeyValueUpdateProcessor<? super Integer, ? super SerializedStubTree> addProcessor,
                               @NotNull KeyValueUpdateProcessor<? super Integer, ? super SerializedStubTree> updateProcessor,
                               @NotNull RemovedKeyProcessor<? super Integer> removeProcessor,
                               boolean dryRun) throws StorageException {
    if (FileBasedIndexEx.TRACE_STUB_INDEX_UPDATES) {
      LOG.info((dryRun ? "[dry run]" : "") + "differentiate: inputId=" + myInputId +
               ",newData.isEmpty=" + newData.isEmpty() +
               ", myCurrentTree is " + ((myCurrentTree == null) ? "null" : "not null"));
    }

    if (!newData.isEmpty()) {
      SerializedStubTree newSerializedStubTree = newData.values().iterator().next();
      if (myCurrentTree != null) {
        if (treesAreEqual(newSerializedStubTree, myCurrentTree)) {
          if (FileBasedIndexEx.TRACE_STUB_INDEX_UPDATES) {
            LOG.info((dryRun ? "[dry run]" : "") + "equal trees: inputId=" + myInputId +
                     ",myTreeLen=" + myCurrentTree.myTreeByteLength +
                     ",myStubLen=" + myCurrentTree.myIndexedStubByteLength +
                     ",newTreeLen=" + newSerializedStubTree.myIndexedStubByteLength +
                     ",newStubLen=" + newSerializedStubTree.myIndexedStubByteLength);
          }
          return false;
        }
        else {
          if (FileBasedIndexEx.TRACE_STUB_INDEX_UPDATES) {
            LOG.info((dryRun ? "[dry run]" : "") + "different trees: inputId=" + myInputId +
                     ",myStubLen=" + myCurrentTree.myIndexedStubByteLength +
                     ",newStubLen=" + newSerializedStubTree.myIndexedStubByteLength);
          }
        }
        removeProcessor.process(myInputId, myInputId);
      }
      addProcessor.process(myInputId, newSerializedStubTree, myInputId);
      if (!dryRun) updateStubIndices(newSerializedStubTree);
    }
    else {
      if (myCurrentTree == null) {
        if (FileBasedIndexEx.TRACE_STUB_INDEX_UPDATES) {
          LOG.info((dryRun ? "[dry run]" : "") + "myCurrentTree=null, inputId=" + myInputId);
        }
        return false; // ?????????
      }
      removeProcessor.process(myInputId, myInputId);
      if (!dryRun) updateStubIndices(null);
    }
    return true;
  }
public boolean differentiate(@NotNull Map<Integer, SerializedStubTree> newData,
                               @NotNull KeyValueUpdateProcessor<? super Integer, ? super SerializedStubTree> addProcessor,
                               @NotNull KeyValueUpdateProcessor<? super Integer, ? super SerializedStubTree> updateProcessor,
                               @NotNull RemovedKeyProcessor<? super Integer> removeProcessor,
                               boolean dryRun)