<commit_before>// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.siyeh.ipp.expression;

import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.util.Pass;
import com.intellij.psi.*;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.PsiExpressionTrimRenderer;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.refactoring.IntroduceTargetChooser;
import com.intellij.util.ArrayUtil;
import com.intellij.util.ObjectUtils;
import com.siyeh.ig.PsiReplacementUtil;
import com.siyeh.ig.psiutils.CommentTracker;
import com.siyeh.ig.psiutils.ParenthesesUtils;
import com.siyeh.ipp.base.Intention;
import com.siyeh.ipp.base.PsiElementPredicate;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.ArrayList;
import java.util.List;
import java.util.function.BiFunction;

public abstract class ExpandBracketsBaseIntention extends Intention {

  @Override
  protected void processIntention(@NotNull PsiElement element) {
    processIntention(null, element);
  }

  @Override
  protected void processIntention(Editor editor, @NotNull PsiElement element) {
    final List<PsiExpression> possibleInnerExpressions = getPossibleInnerExpressions(element);
    processInnerExpression(editor, possibleInnerExpressions);
  }

  private void processInnerExpression(@Nullable Editor editor, @NotNull List<PsiExpression> expressions) {
    if (expressions.size() == 1) {
      replaceExpression(expressions.get(0));
      return;
    }

    if (expressions.isEmpty() || editor == null) return;

    final PsiExpressionTrimRenderer.RenderFunction renderer = new PsiExpressionTrimRenderer.RenderFunction();
    final Pass<PsiExpression> callback = new Pass<PsiExpression>() {
      @Override
      public void pass(PsiExpression expression) {
        WriteCommandAction.runWriteCommandAction(editor.getProject(), () -> replaceExpression(expression));
      }
    };

    IntroduceTargetChooser.showChooser(editor, expressions, callback, renderer);
  }

  private void replaceExpression(@NotNull PsiExpression innerExpr) {
    while (innerExpr.getParent() instanceof PsiPrefixExpression) innerExpr = (PsiExpression)innerExpr.getParent();

    PsiExpression operand = getPolyadicParent(innerExpr, getConjunctionTokens());
    if (operand == null) operand = innerExpr;

    final PsiPolyadicExpression parent = getPolyadicParent(operand, getDisjunctionTokens());

    final String replacement = expandBrackets(parent, operand, innerExpr);
    if (replacement == null) return;

    final CommentTracker commentTracker = new CommentTracker();

    if (parent == null) {
      PsiReplacementUtil.replaceExpression(operand, replacement, commentTracker);
    }
    else {
      PsiReplacementUtil.replaceExpression(parent, replacement, commentTracker);
    }
  }

  @Nullable
  private String expandBrackets(@Nullable PsiPolyadicExpression outerExpr,
                                @NotNull PsiExpression bracketsOperand,
                                @NotNull PsiExpression innerExpr) {
    final StringBuilder sb = new StringBuilder();

    if (outerExpr == null) {
      if (!addReplacement(null, bracketsOperand, innerExpr, sb)) return null;
      return sb.toString();
    }

    for (PsiExpression operand : outerExpr.getOperands()) {
      final PsiJavaToken token = outerExpr.getTokenBeforeOperand(operand);

      if (operand != bracketsOperand) {
        if (token != null) sb.append(token.getText());
        sb.append(operand.getText());
        continue;
      }

      if (!addReplacement(token, bracketsOperand, innerExpr, sb)) return null;
    }

    return sb.toString();
  }

  /**
   * Transforms operand by expanding brackets for innerExpr
   * and appends transformed operand to sb (e.g. {@code} <b>a * (b + c) -> a * b + a * c</b>).
   * <p>Note that operand can be equal to innerExpr in case if innerExpr is inside disjunction expression (e.g. <b>a + (b + c)</b>).
   *
   * @param token     token before operand. possibly null if operand is first in expression or not in expression at all
   * @param operand   operand with innerExpr. might be equal to innerExpr
   * @param innerExpr parenthesised expression with prefixes if any
   * @param sb        string builder to append result to
   * @return true if appended, false otherwise
   */
  protected abstract boolean addReplacement(@Nullable PsiJavaToken token, @NotNull PsiExpression operand,
                                            @NotNull PsiExpression innerExpr, @NotNull StringBuilder sb);

  @NotNull
  protected abstract IElementType[] getConjunctionTokens();

  @NotNull
  protected abstract IElementType[] getDisjunctionTokens();

  @NotNull
  protected abstract IElementType[] getPrefixes();

  @NotNull
  @Override
  protected PsiElementPredicate getElementPredicate() {
    return new ExpandBracketsPredicate(ArrayUtil.mergeArrays(getConjunctionTokens(), getDisjunctionTokens()), getPrefixes());
  }

  @NotNull
  private List<PsiExpression> getPossibleInnerExpressions(@NotNull PsiElement element) {
    final List<PsiExpression> possibleExpressions = new ArrayList<>();

    if (!(element instanceof PsiJavaToken)) return possibleExpressions;

    while ((element = PsiTreeUtil.getParentOfType(element, PsiParenthesizedExpression.class)) != null) {
      if (!ParenthesesUtils.areParenthesesNeeded((PsiParenthesizedExpression)element, false)) continue;

      final PsiElement parent = getFirstNonPrefixedParent(element);
      if (parent == null) continue;

      possibleExpressions.add((PsiExpression)element);

      element = parent;
    }

    return possibleExpressions;
  }
<commit_msg>ExpandBracketsIntention: acquire write only after choosing bracket to expand (IDEA-206609)<commit_after>// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.siyeh.ipp.expression;

import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.util.Pass;
import com.intellij.psi.*;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.PsiExpressionTrimRenderer;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.refactoring.IntroduceTargetChooser;
import com.intellij.util.ArrayUtil;
import com.intellij.util.ObjectUtils;
import com.siyeh.ig.PsiReplacementUtil;
import com.siyeh.ig.psiutils.CommentTracker;
import com.siyeh.ig.psiutils.ParenthesesUtils;
import com.siyeh.ipp.base.Intention;
import com.siyeh.ipp.base.PsiElementPredicate;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.ArrayList;
import java.util.List;
import java.util.function.BiFunction;

public abstract class ExpandBracketsBaseIntention extends Intention {

  @Override
  protected void processIntention(@NotNull PsiElement element) {
    processIntention(null, element);
  }

  @Override
  protected void processIntention(Editor editor, @NotNull PsiElement element) {
    final List<PsiExpression> possibleInnerExpressions = getPossibleInnerExpressions(element);
    processInnerExpression(editor, possibleInnerExpressions);
  }

  private void processInnerExpression(@Nullable Editor editor, @NotNull List<PsiExpression> expressions) {
    final Pass<PsiExpression> callback = new Pass<PsiExpression>() {
      @Override
      public void pass(PsiExpression expression) {
        WriteCommandAction.writeCommandAction(expression.getProject(), expression.getContainingFile())
          .run(() -> replaceExpression(expression));
      }
    };

    if (expressions.size() == 1) {
      callback.pass(expressions.get(0));
      return;
    }

    if (expressions.isEmpty() || editor == null) return;

    final PsiExpressionTrimRenderer.RenderFunction renderer = new PsiExpressionTrimRenderer.RenderFunction();
    IntroduceTargetChooser.showChooser(editor, expressions, callback, renderer);
  }

  private void replaceExpression(@NotNull PsiExpression innerExpr) {
    while (innerExpr.getParent() instanceof PsiPrefixExpression) innerExpr = (PsiExpression)innerExpr.getParent();

    PsiExpression operand = getPolyadicParent(innerExpr, getConjunctionTokens());
    if (operand == null) operand = innerExpr;

    final PsiPolyadicExpression parent = getPolyadicParent(operand, getDisjunctionTokens());

    final String replacement = expandBrackets(parent, operand, innerExpr);
    if (replacement == null) return;

    final CommentTracker commentTracker = new CommentTracker();

    if (parent == null) {
      PsiReplacementUtil.replaceExpression(operand, replacement, commentTracker);
    }
    else {
      PsiReplacementUtil.replaceExpression(parent, replacement, commentTracker);
    }
  }

  @Nullable
  private String expandBrackets(@Nullable PsiPolyadicExpression outerExpr,
                                @NotNull PsiExpression bracketsOperand,
                                @NotNull PsiExpression innerExpr) {
    final StringBuilder sb = new StringBuilder();

    if (outerExpr == null) {
      if (!addReplacement(null, bracketsOperand, innerExpr, sb)) return null;
      return sb.toString();
    }

    for (PsiExpression operand : outerExpr.getOperands()) {
      final PsiJavaToken token = outerExpr.getTokenBeforeOperand(operand);

      if (operand != bracketsOperand) {
        if (token != null) sb.append(token.getText());
        sb.append(operand.getText());
        continue;
      }

      if (!addReplacement(token, bracketsOperand, innerExpr, sb)) return null;
    }

    return sb.toString();
  }

  /**
   * Transforms operand by expanding brackets for innerExpr
   * and appends transformed operand to sb (e.g. {@code} <b>a * (b + c) -> a * b + a * c</b>).
   * <p>Note that operand can be equal to innerExpr in case if innerExpr is inside disjunction expression (e.g. <b>a + (b + c)</b>).
   *
   * @param token     token before operand. possibly null if operand is first in expression or not in expression at all
   * @param operand   operand with innerExpr. might be equal to innerExpr
   * @param innerExpr parenthesised expression with prefixes if any
   * @param sb        string builder to append result to
   * @return true if appended, false otherwise
   */
  protected abstract boolean addReplacement(@Nullable PsiJavaToken token, @NotNull PsiExpression operand,
                                            @NotNull PsiExpression innerExpr, @NotNull StringBuilder sb);

  @NotNull
  protected abstract IElementType[] getConjunctionTokens();

  @NotNull
  protected abstract IElementType[] getDisjunctionTokens();

  @NotNull
  protected abstract IElementType[] getPrefixes();

  @Override
  public boolean startInWriteAction() {
    return false;
  }

  @NotNull
  @Override
  protected PsiElementPredicate getElementPredicate() {
    return new ExpandBracketsPredicate(ArrayUtil.mergeArrays(getConjunctionTokens(), getDisjunctionTokens()), getPrefixes());
  }

  @NotNull
  private List<PsiExpression> getPossibleInnerExpressions(@NotNull PsiElement element) {
    final List<PsiExpression> possibleExpressions = new ArrayList<>();

    if (!(element instanceof PsiJavaToken)) return possibleExpressions;

    while ((element = PsiTreeUtil.getParentOfType(element, PsiParenthesizedExpression.class)) != null) {
      if (!ParenthesesUtils.areParenthesesNeeded((PsiParenthesizedExpression)element, false)) continue;

      final PsiElement parent = getFirstNonPrefixedParent(element);
      if (parent == null) continue;

      possibleExpressions.add((PsiExpression)element);

      element = parent;
    }

    return possibleExpressions;
  }

{'fraction_non_alphanumeric': 0.09222316145393068, 'fraction_numerical': 0.00253592561284869, 'mean_word_length': 4.170891608391608, 'pattern_counts': {'":': 0, '<': 27, '<?xml version=': 0, '>': 31, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6545389', 'n_tokens_mistral': 3304, 'n_tokens_neox': 3172, 'n_words': 839}