@NotNull
  public static DiffLog mergeTrees(
      @NotNull final PsiFileImpl fileImpl,
      @NotNull final ASTNode oldRoot,
      @NotNull final ASTNode newRoot,
      @NotNull ProgressIndicator indicator) {
    if (newRoot instanceof FileElement) {
      ((FileElement) newRoot).setCharTable(fileImpl.getTreeElement().getCharTable());
    }

    try {
      newRoot.putUserData(TREE_TO_BE_REPARSED, oldRoot);
      if (isReplaceWholeNode(fileImpl, newRoot)) {
        DiffLog treeChangeEvent =
            replaceElementWithEvents((CompositeElement) oldRoot, (CompositeElement) newRoot);
        fileImpl.putUserData(TREE_DEPTH_LIMIT_EXCEEDED, Boolean.TRUE);

        return treeChangeEvent;
      }
      newRoot
          .getFirstChildNode(); // maybe reparsed in PsiBuilderImpl and have thrown exception here
    } catch (ReparsedSuccessfullyException e) {
      // reparsed in PsiBuilderImpl
      return e.getDiffLog();
    } finally {
      newRoot.putUserData(TREE_TO_BE_REPARSED, null);
    }

    final ASTShallowComparator comparator = new ASTShallowComparator(indicator);
    final ASTStructure treeStructure = createInterruptibleASTStructure(newRoot, indicator);

    DiffLog diffLog = new DiffLog();
    diffTrees(oldRoot, diffLog, comparator, treeStructure, indicator);
    return diffLog;
  }
  @NotNull
  private static DiffLog makeFullParse(
      @NotNull PsiFileImpl fileImpl,
      @NotNull CharSequence newFileText,
      @NotNull ProgressIndicator indicator,
      @NotNull CharSequence lastCommittedText) {
    if (fileImpl instanceof PsiCodeFragment) {
      FileElement parent = fileImpl.getTreeElement();
      final FileElement holderElement =
          new DummyHolder(fileImpl.getManager(), null).getTreeElement();
      holderElement.rawAddChildren(
          fileImpl.createContentLeafElement(
              holderElement.getCharTable().intern(newFileText, 0, newFileText.length())));
      DiffLog diffLog = new DiffLog();
      diffLog.appendReplaceFileElement(parent, (FileElement) holderElement.getFirstChildNode());

      return diffLog;
    } else {
      FileViewProvider viewProvider = fileImpl.getViewProvider();
      viewProvider.getLanguages();
      FileType fileType = viewProvider.getVirtualFile().getFileType();
      String fileName = fileImpl.getName();
      final LightVirtualFile lightFile =
          new LightVirtualFile(
              fileName,
              fileType,
              newFileText,
              viewProvider.getVirtualFile().getCharset(),
              fileImpl.getViewProvider().getModificationStamp());
      lightFile.setOriginalFile(viewProvider.getVirtualFile());

      FileViewProvider copy = viewProvider.createCopy(lightFile);
      if (copy.isEventSystemEnabled()) {
        throw new AssertionError(
            "Copied view provider must be non-physical for reparse to deliver correct events: "
                + viewProvider);
      }
      copy.getLanguages();
      SingleRootFileViewProvider.doNotCheckFileSizeLimit(
          lightFile); // optimization: do not convert file contents to bytes to determine if we
                      // should codeinsight it
      PsiFileImpl newFile = getFileCopy(fileImpl, copy);

      newFile.setOriginalFile(fileImpl);

      final FileElement newFileElement = (FileElement) newFile.getNode();
      final FileElement oldFileElement = (FileElement) fileImpl.getNode();
      if (!lastCommittedText.toString().equals(oldFileElement.getText())) {
        throw new IncorrectOperationException();
      }
      DiffLog diffLog =
          mergeTrees(fileImpl, oldFileElement, newFileElement, indicator, lastCommittedText);

      ((PsiManagerEx) fileImpl.getManager()).getFileManager().setViewProvider(lightFile, null);
      return diffLog;
    }
  }
  @Override
  @NotNull
  public ASTNode getNode() {
    ASTNode node = myNode;
    if (node == null) {
      ApplicationManager.getApplication().assertReadAccessAllowed();
      PsiFileImpl file = (PsiFileImpl) getContainingFile();
      synchronized (file.getStubLock()) {
        node = myNode;
        if (node == null) {
          NonCancelableSection criticalSection =
              ProgressIndicatorProvider.startNonCancelableSectionIfSupported();
          try {
            if (!file.isValid()) throw new PsiInvalidElementAccessException(this);
            FileElement treeElement = file.getTreeElement();
            StubTree stubTree = file.getStubTree();
            if (treeElement != null) {
              return notBoundInExistingAst(file, treeElement, stubTree);
            }
            final FileElement fileElement = file.loadTreeElement();
            node = myNode;
            if (node == null) {
              @NonNls
              String message =
                  "Failed to bind stub to AST for element "
                      + getClass()
                      + " in "
                      + (file.getVirtualFile() == null
                          ? "<unknown file>"
                          : file.getVirtualFile().getPath())
                      + "\nFile stub tree:\n"
                      + (stubTree != null
                          ? StringUtil.trimLog(
                              ((PsiFileStubImpl) stubTree.getRoot()).printTree(), 1024)
                          : " is null")
                      + "\nLoaded file AST:\n"
                      + StringUtil.trimLog(DebugUtil.treeToString(fileElement, true), 1024);
              throw new IllegalArgumentException(message);
            }
          } finally {
            criticalSection.done();
          }
        }
      }
    }

    return node;
  }
  @Override
  @NotNull
  public DiffLog reparseRange(
      @NotNull final PsiFile file,
      @NotNull TextRange changedPsiRange,
      @NotNull final CharSequence newFileText,
      @NotNull final ProgressIndicator indicator) {
    final PsiFileImpl fileImpl = (PsiFileImpl) file;

    final Couple<ASTNode> reparseableRoots =
        findReparseableRoots(fileImpl, changedPsiRange, newFileText);
    return reparseableRoots != null
        ? mergeTrees(fileImpl, reparseableRoots.first, reparseableRoots.second, indicator)
        : makeFullParse(
            fileImpl.getTreeElement(), newFileText, newFileText.length(), fileImpl, indicator);
  }
  /**
   * This method searches ast node that could be reparsed incrementally and returns pair of target
   * reparseable node and new replacement node. Returns null if there is no any chance to make
   * incremental parsing.
   */
  @Nullable
  public Couple<ASTNode> findReparseableRoots(
      @NotNull PsiFileImpl file,
      @NotNull TextRange changedPsiRange,
      @NotNull CharSequence newFileText) {
    Project project = file.getProject();
    final FileElement fileElement = file.getTreeElement();
    final CharTable charTable = fileElement.getCharTable();
    int lengthShift = newFileText.length() - fileElement.getTextLength();

    if (fileElement.getElementType() instanceof ITemplateDataElementType || isTooDeep(file)) {
      // unable to perform incremental reparse for template data in JSP, or in exceptionally deep
      // trees
      return null;
    }

    final ASTNode leafAtStart =
        fileElement.findLeafElementAt(Math.max(0, changedPsiRange.getStartOffset() - 1));
    final ASTNode leafAtEnd =
        fileElement.findLeafElementAt(
            Math.min(changedPsiRange.getEndOffset(), fileElement.getTextLength() - 1));
    ASTNode node =
        leafAtStart != null && leafAtEnd != null
            ? TreeUtil.findCommonParent(leafAtStart, leafAtEnd)
            : fileElement;
    Language baseLanguage = file.getViewProvider().getBaseLanguage();

    while (node != null && !(node instanceof FileElement)) {
      IElementType elementType = node.getElementType();
      if (elementType instanceof IReparseableElementType) {
        final TextRange textRange = node.getTextRange();
        final IReparseableElementType reparseable = (IReparseableElementType) elementType;

        if (baseLanguage.isKindOf(reparseable.getLanguage())
            && textRange.getLength() + lengthShift > 0) {
          final int start = textRange.getStartOffset();
          final int end = start + textRange.getLength() + lengthShift;
          if (end > newFileText.length()) {
            reportInconsistentLength(file, newFileText, node, start, end);
            break;
          }

          CharSequence newTextStr = newFileText.subSequence(start, end);

          if (reparseable.isParsable(node.getTreeParent(), newTextStr, baseLanguage, project)) {
            ASTNode chameleon = reparseable.createNode(newTextStr);
            if (chameleon != null) {
              DummyHolder holder =
                  DummyHolderFactory.createHolder(
                      file.getManager(), null, node.getPsi(), charTable);
              holder.getTreeElement().rawAddChildren((TreeElement) chameleon);

              if (holder.getTextLength() != newTextStr.length()) {
                String details =
                    ApplicationManager.getApplication().isInternal()
                        ? "text=" + newTextStr + "; treeText=" + holder.getText() + ";"
                        : "";
                LOG.error("Inconsistent reparse: " + details + " type=" + elementType);
              }

              return Couple.of(node, chameleon);
            }
          }
        }
      }
      node = node.getTreeParent();
    }
    return null;
  }