<commit_before>/*
 * Copyright 2000-2017 JetBrains s.r.o.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.intellij.refactoring.extractMethod;

import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Pair;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.codeStyle.SuggestedNameInfo;
import com.intellij.psi.codeStyle.VariableKind;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.refactoring.introduceField.ElementToWorkOn;
import com.intellij.refactoring.introduceParameter.IntroduceParameterHandler;
import com.intellij.refactoring.util.VariableData;
import com.intellij.refactoring.util.duplicates.*;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.text.UniqueNameGenerator;
import gnu.trove.THashMap;
import gnu.trove.THashSet;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.*;

import static com.intellij.refactoring.extractMethod.ExtractMethodHandler.REFACTORING_NAME;

/**
 * @author Pavel.Dolgov
 */
public class ParametrizedDuplicates {
  private static final Logger LOG = Logger.getInstance(ParametrizedDuplicates.class);

  private final PsiElement[] myElements;
  private List<Match> myMatches;
  private List<ClusterOfUsages> myUsagesList;
  private PsiMethod myParametrizedMethod;
  private PsiMethodCallExpression myParametrizedCall;
  private VariableData[] myVariableDatum;

  private ParametrizedDuplicates(@NotNull PsiElement[] pattern,
                                 @NotNull ExtractMethodProcessor originalProcessor) {
    pattern = getFilteredElements(pattern);
    PsiElement firstElement = pattern.length != 0 ? pattern[0] : null;
    if (firstElement instanceof PsiStatement) {
      PsiElement[] copy = copyElements(pattern);
      myElements = wrapWithCodeBlock(copy, originalProcessor.getInputVariables());
    }
    else if (firstElement instanceof PsiExpression) {
      PsiElement[] copy = copyElements(pattern);
      PsiExpression wrapped = wrapExpressionWithCodeBlock(copy, originalProcessor);
      myElements = wrapped != null ? new PsiElement[]{wrapped} : PsiElement.EMPTY_ARRAY;
    }
    else {
      myElements = PsiElement.EMPTY_ARRAY;
    }
  }

  private static PsiElement[] copyElements(@NotNull PsiElement[] pattern) {
    Project project = pattern[0].getProject();
    return IntroduceParameterHandler.getElementsInCopy(project, pattern[0].getContainingFile(), pattern, false);
  }

  @Nullable
  public static ParametrizedDuplicates findDuplicates(@NotNull ExtractMethodProcessor originalProcessor) {
    PsiElement[] pattern = originalProcessor.myElements;
    if (pattern.length == 0) {
      return null;
    }

    DuplicatesFinder finder = createDuplicatesFinder(originalProcessor, DuplicatesFinder.MatchType.PARAMETRIZED);
    List<Match> matches = finder.findDuplicates(originalProcessor.myTargetClass);
    matches = filterNestedSubexpressions(matches);
    if (matches.isEmpty()) {
      return null;
    }

    Map<PsiExpression, String> predefinedNames = foldParameters(originalProcessor, matches);

    ParametrizedDuplicates duplicates = new ParametrizedDuplicates(pattern, originalProcessor);
    if (!duplicates.initMatches(pattern, matches)) {
      return null;
    }

    if (!duplicates.extract(originalProcessor, predefinedNames)) {
      return null;
    }
    return duplicates;
  }

  @NotNull
  private static Map<PsiExpression, String> foldParameters(ExtractMethodProcessor originalProcessor, List<Match> matches) {
    if (matches.isEmpty() || !originalProcessor.getInputVariables().isFoldable()) {
      return Collections.emptyMap();
    }

    // As folded parameters don't work along with extracted parameters we need to apply the finder again to actually fold the parameters
    DuplicatesFinder finder = createDuplicatesFinder(originalProcessor, DuplicatesFinder.MatchType.FOLDED);
    Map<Match, Match> foldedMatches = new HashMap<>();
    Map<DuplicatesFinder.Parameter, VariableData> parametersToFold = new LinkedHashMap<>();
    for (VariableData data : originalProcessor.getInputVariables().getInputVariables()) {
      parametersToFold.put(new DuplicatesFinder.Parameter(data.variable, data.type, true), data);
    }

    for (Match match : matches) {
      Match foldedMatch = finder.isDuplicate(match.getMatchStart(), false);
      LOG.assertTrue(foldedMatch != null, "folded match should exist");
      LOG.assertTrue(match.getMatchStart() == foldedMatch.getMatchStart() &&
                     match.getMatchEnd() == foldedMatch.getMatchEnd(), "folded match range should be the same");
      foldedMatches.put(match, foldedMatch);

      parametersToFold.keySet().removeIf(parameter -> !canFoldParameter(match, foldedMatch, parameter));
    }
    if (parametersToFold.isEmpty()) {
      return Collections.emptyMap();
    }

    Map<PsiExpression, String> predefinedNames = new HashMap<>();
    for (Match match : matches) {
      Match foldedMatch = foldedMatches.get(match);
      LOG.assertTrue(foldedMatch != null, "folded match");

      for (Map.Entry<DuplicatesFinder.Parameter, VariableData> entry : parametersToFold.entrySet()) {
        DuplicatesFinder.Parameter parameter = entry.getKey();
        VariableData variableData = entry.getValue();
        List<Pair.NonNull<PsiExpression, PsiExpression>> expressionMappings = foldedMatch.getFoldedExpressionMappings(parameter);
        LOG.assertTrue(!ContainerUtil.isEmpty(expressionMappings), "foldedExpressionMappings can't be empty");
        PsiType type = parameter.getType();

        ExtractedParameter extractedParameter = null;
        for (Pair.NonNull<PsiExpression, PsiExpression> expressionMapping : expressionMappings) {
          PsiExpression patternExpression = expressionMapping.getFirst();
          ExtractableExpressionPart patternPart = ExtractableExpressionPart.fromUsage(patternExpression, type);
          if (extractedParameter == null) {
            PsiExpression candidateExpression = expressionMapping.getSecond();
            ExtractableExpressionPart candidatePart = ExtractableExpressionPart.fromUsage(candidateExpression, type);
            extractedParameter = new ExtractedParameter(patternPart, candidatePart, type);
          }
          else {
            extractedParameter.addUsages(patternPart);
          }
          predefinedNames.put(patternExpression, variableData.name);
        }
        LOG.assertTrue(extractedParameter != null, "extractedParameter can't be null");
        match.getExtractedParameters().add(extractedParameter);
      }
    }

    return predefinedNames;
  }

  private static boolean canFoldParameter(Match match, Match foldedMatch, DuplicatesFinder.Parameter parameter) {
    List<Pair.NonNull<PsiExpression, PsiExpression>> expressionMappings = foldedMatch.getFoldedExpressionMappings(parameter);
    if (ContainerUtil.isEmpty(expressionMappings)) {
      return false;
    }
    // Extracted parameters and folded parameters shouldn't overlap
    for (Pair.NonNull<PsiExpression, PsiExpression> expressionMapping : expressionMappings) {
      PsiExpression patternExpression = expressionMapping.getFirst();
      for (ExtractedParameter extractedParameter : match.getExtractedParameters()) {
        for (PsiExpression extractedUsage : extractedParameter.myPatternUsages) {
          if (PsiTreeUtil.isAncestor(patternExpression, extractedUsage, false) ||
              PsiTreeUtil.isAncestor(extractedUsage, patternExpression, false)) {
            return false;
          }
        }
      }
    }
    return true;
  }

  @NotNull
  private static DuplicatesFinder createDuplicatesFinder(@NotNull ExtractMethodProcessor processor,
                                                         @NotNull DuplicatesFinder.MatchType matchType) {
    PsiElement[] elements = getFilteredElements(processor.myElements);
    Set<PsiVariable> effectivelyLocal = processor.getEffectivelyLocalVariables();

    InputVariables inputVariables = matchType == DuplicatesFinder.MatchType.PARAMETRIZED
                                    ? processor.myInputVariables.copyWithoutFolding() : processor.myInputVariables;
    ReturnValue returnValue = processor.myOutputVariable != null ? new VariableReturnValue(processor.myOutputVariable) : null;
    return new DuplicatesFinder(elements, inputVariables, returnValue,
                                Collections.emptyList(), matchType, effectivelyLocal);
  }

  @NotNull
  public PsiMethod replaceMethod(@NotNull PsiMethod originalMethod) {
    PsiElementFactory factory = JavaPsiFacade.getElementFactory(originalMethod.getProject());
    String text = myParametrizedMethod.getText();
    PsiMethod method = factory.createMethodFromText(text, originalMethod.getParent());
    return (PsiMethod)originalMethod.replace(method);
  }<commit_msg>Java: Don't search duplicates of empty code (EA-121171)<commit_after>/*
 * Copyright 2000-2017 JetBrains s.r.o.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.intellij.refactoring.extractMethod;

import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Pair;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.codeStyle.SuggestedNameInfo;
import com.intellij.psi.codeStyle.VariableKind;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.refactoring.introduceField.ElementToWorkOn;
import com.intellij.refactoring.introduceParameter.IntroduceParameterHandler;
import com.intellij.refactoring.util.VariableData;
import com.intellij.refactoring.util.duplicates.*;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.text.UniqueNameGenerator;
import gnu.trove.THashMap;
import gnu.trove.THashSet;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.*;

import static com.intellij.refactoring.extractMethod.ExtractMethodHandler.REFACTORING_NAME;

/**
 * @author Pavel.Dolgov
 */
public class ParametrizedDuplicates {
  private static final Logger LOG = Logger.getInstance(ParametrizedDuplicates.class);

  private final PsiElement[] myElements;
  private List<Match> myMatches;
  private List<ClusterOfUsages> myUsagesList;
  private PsiMethod myParametrizedMethod;
  private PsiMethodCallExpression myParametrizedCall;
  private VariableData[] myVariableDatum;

  private ParametrizedDuplicates(@NotNull PsiElement[] pattern,
                                 @NotNull ExtractMethodProcessor originalProcessor) {
    pattern = getFilteredElements(pattern);
    PsiElement firstElement = pattern.length != 0 ? pattern[0] : null;
    if (firstElement instanceof PsiStatement) {
      PsiElement[] copy = copyElements(pattern);
      myElements = wrapWithCodeBlock(copy, originalProcessor.getInputVariables());
    }
    else if (firstElement instanceof PsiExpression) {
      PsiElement[] copy = copyElements(pattern);
      PsiExpression wrapped = wrapExpressionWithCodeBlock(copy, originalProcessor);
      myElements = wrapped != null ? new PsiElement[]{wrapped} : PsiElement.EMPTY_ARRAY;
    }
    else {
      myElements = PsiElement.EMPTY_ARRAY;
    }
  }

  private static PsiElement[] copyElements(@NotNull PsiElement[] pattern) {
    Project project = pattern[0].getProject();
    return IntroduceParameterHandler.getElementsInCopy(project, pattern[0].getContainingFile(), pattern, false);
  }

  @Nullable
  public static ParametrizedDuplicates findDuplicates(@NotNull ExtractMethodProcessor originalProcessor) {
    DuplicatesFinder finder = createDuplicatesFinder(originalProcessor, DuplicatesFinder.MatchType.PARAMETRIZED);
    if (finder == null) {
      return null;
    }
    List<Match> matches = finder.findDuplicates(originalProcessor.myTargetClass);
    matches = filterNestedSubexpressions(matches);
    if (matches.isEmpty()) {
      return null;
    }

    Map<PsiExpression, String> predefinedNames = foldParameters(originalProcessor, matches);

    PsiElement[] pattern = originalProcessor.myElements;
    ParametrizedDuplicates duplicates = new ParametrizedDuplicates(pattern, originalProcessor);
    if (!duplicates.initMatches(pattern, matches)) {
      return null;
    }

    if (!duplicates.extract(originalProcessor, predefinedNames)) {
      return null;
    }
    return duplicates;
  }

  @NotNull
  private static Map<PsiExpression, String> foldParameters(ExtractMethodProcessor originalProcessor, List<Match> matches) {
    if (matches.isEmpty() || !originalProcessor.getInputVariables().isFoldable()) {
      return Collections.emptyMap();
    }

    // As folded parameters don't work along with extracted parameters we need to apply the finder again to actually fold the parameters
    DuplicatesFinder finder = createDuplicatesFinder(originalProcessor, DuplicatesFinder.MatchType.FOLDED);
    if (finder == null) {
      return Collections.emptyMap();
    }
    Map<Match, Match> foldedMatches = new HashMap<>();
    Map<DuplicatesFinder.Parameter, VariableData> parametersToFold = new LinkedHashMap<>();
    for (VariableData data : originalProcessor.getInputVariables().getInputVariables()) {
      parametersToFold.put(new DuplicatesFinder.Parameter(data.variable, data.type, true), data);
    }

    for (Match match : matches) {
      Match foldedMatch = finder.isDuplicate(match.getMatchStart(), false);
      LOG.assertTrue(foldedMatch != null, "folded match should exist");
      LOG.assertTrue(match.getMatchStart() == foldedMatch.getMatchStart() &&
                     match.getMatchEnd() == foldedMatch.getMatchEnd(), "folded match range should be the same");
      foldedMatches.put(match, foldedMatch);

      parametersToFold.keySet().removeIf(parameter -> !canFoldParameter(match, foldedMatch, parameter));
    }
    if (parametersToFold.isEmpty()) {
      return Collections.emptyMap();
    }

    Map<PsiExpression, String> predefinedNames = new HashMap<>();
    for (Match match : matches) {
      Match foldedMatch = foldedMatches.get(match);
      LOG.assertTrue(foldedMatch != null, "folded match");

      for (Map.Entry<DuplicatesFinder.Parameter, VariableData> entry : parametersToFold.entrySet()) {
        DuplicatesFinder.Parameter parameter = entry.getKey();
        VariableData variableData = entry.getValue();
        List<Pair.NonNull<PsiExpression, PsiExpression>> expressionMappings = foldedMatch.getFoldedExpressionMappings(parameter);
        LOG.assertTrue(!ContainerUtil.isEmpty(expressionMappings), "foldedExpressionMappings can't be empty");
        PsiType type = parameter.getType();

        ExtractedParameter extractedParameter = null;
        for (Pair.NonNull<PsiExpression, PsiExpression> expressionMapping : expressionMappings) {
          PsiExpression patternExpression = expressionMapping.getFirst();
          ExtractableExpressionPart patternPart = ExtractableExpressionPart.fromUsage(patternExpression, type);
          if (extractedParameter == null) {
            PsiExpression candidateExpression = expressionMapping.getSecond();
            ExtractableExpressionPart candidatePart = ExtractableExpressionPart.fromUsage(candidateExpression, type);
            extractedParameter = new ExtractedParameter(patternPart, candidatePart, type);
          }
          else {
            extractedParameter.addUsages(patternPart);
          }
          predefinedNames.put(patternExpression, variableData.name);
        }
        LOG.assertTrue(extractedParameter != null, "extractedParameter can't be null");
        match.getExtractedParameters().add(extractedParameter);
      }
    }

    return predefinedNames;
  }

  private static boolean canFoldParameter(Match match, Match foldedMatch, DuplicatesFinder.Parameter parameter) {
    List<Pair.NonNull<PsiExpression, PsiExpression>> expressionMappings = foldedMatch.getFoldedExpressionMappings(parameter);
    if (ContainerUtil.isEmpty(expressionMappings)) {
      return false;
    }
    // Extracted parameters and folded parameters shouldn't overlap
    for (Pair.NonNull<PsiExpression, PsiExpression> expressionMapping : expressionMappings) {
      PsiExpression patternExpression = expressionMapping.getFirst();
      for (ExtractedParameter extractedParameter : match.getExtractedParameters()) {
        for (PsiExpression extractedUsage : extractedParameter.myPatternUsages) {
          if (PsiTreeUtil.isAncestor(patternExpression, extractedUsage, false) ||
              PsiTreeUtil.isAncestor(extractedUsage, patternExpression, false)) {
            return false;
          }
        }
      }
    }
    return true;
  }

  @Nullable
  private static DuplicatesFinder createDuplicatesFinder(@NotNull ExtractMethodProcessor processor,
                                                         @NotNull DuplicatesFinder.MatchType matchType) {
    PsiElement[] elements = getFilteredElements(processor.myElements);
    if (elements.length == 0) {
      return null;
    }
    Set<PsiVariable> effectivelyLocal = processor.getEffectivelyLocalVariables();

    InputVariables inputVariables = matchType == DuplicatesFinder.MatchType.PARAMETRIZED
                                    ? processor.myInputVariables.copyWithoutFolding() : processor.myInputVariables;
    ReturnValue returnValue = processor.myOutputVariable != null ? new VariableReturnValue(processor.myOutputVariable) : null;
    return new DuplicatesFinder(elements, inputVariables, returnValue,
                                Collections.emptyList(), matchType, effectivelyLocal);
  }

  @NotNull
  public PsiMethod replaceMethod(@NotNull PsiMethod originalMethod) {
    PsiElementFactory factory = JavaPsiFacade.getElementFactory(originalMethod.getProject());
    String text = myParametrizedMethod.getText();
    PsiMethod method = factory.createMethodFromText(text, originalMethod.getParent());
    return (PsiMethod)originalMethod.replace(method);
  }
{'fraction_non_alphanumeric': 0.08945420424334045, 'fraction_numerical': 0.002085179586091852, 'mean_word_length': 4.208797176215042, 'pattern_counts': {'":': 0, '<': 43, '<?xml version=': 0, '>': 45, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 8, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6038553', 'n_tokens_mistral': 5094, 'n_tokens_neox': 4970, 'n_words': 1119}