| /* |
| * Copyright 2000-2014 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 org.jetbrains.plugins.groovy.refactoring.introduce; |
| |
| import com.intellij.openapi.project.Project; |
| import com.intellij.psi.PsiDirectory; |
| import com.intellij.psi.PsiElement; |
| import com.intellij.refactoring.ui.ConflictsDialog; |
| import com.intellij.util.ArrayUtil; |
| import com.intellij.util.containers.MultiMap; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.plugins.groovy.lang.psi.GroovyFileBase; |
| import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrVariable; |
| import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression; |
| import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrTypeDefinition; |
| import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMethod; |
| import org.jetbrains.plugins.groovy.refactoring.GroovyRefactoringUtil; |
| |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Comparator; |
| |
| /** |
| * @author Maxim.Medvedev |
| */ |
| public class GrIntroduceValidatorEngine implements GrIntroduceHandlerBase.Validator { |
| private final GrIntroduceContext myContext; |
| private final ConflictReporter myReporter; |
| |
| public GrIntroduceValidatorEngine(GrIntroduceContext context, ConflictReporter reporter) { |
| myContext = context; |
| myReporter = reporter; |
| } |
| |
| @Override |
| public boolean isOK(GrIntroduceDialog dialog) { |
| final GrIntroduceSettings settings = dialog.getSettings(); |
| if (settings == null) return false; |
| String varName = settings.getName(); |
| boolean allOccurrences = settings.replaceAllOccurrences(); |
| final MultiMap<PsiElement, String> conflicts = isOKImpl(varName, allOccurrences); |
| return conflicts.size() <= 0 || reportConflicts(conflicts, getProject()); |
| } |
| |
| private static boolean reportConflicts(final MultiMap<PsiElement, String> conflicts, final Project project) { |
| ConflictsDialog conflictsDialog = new ConflictsDialog(project, conflicts); |
| conflictsDialog.show(); |
| return conflictsDialog.isOK(); |
| } |
| |
| private MultiMap<PsiElement, String> isOKImpl(String varName, boolean replaceAllOccurrences) { |
| PsiElement firstOccurrence = getFirstOccurrence(replaceAllOccurrences); |
| |
| final MultiMap<PsiElement, String> conflicts = new MultiMap<PsiElement, String>(); |
| assert varName != null; |
| |
| final int offset = firstOccurrence.getTextRange().getStartOffset(); |
| |
| validateOccurrencesDown(myContext.getScope(), conflicts, varName, offset); |
| if (!(myContext.getScope() instanceof GroovyFileBase)) { |
| validateVariableOccurrencesUp(myContext.getScope(), conflicts, varName, offset); |
| } |
| |
| if (replaceAllOccurrences) { |
| for (PsiElement element : myContext.getOccurrences()) { |
| if (element == firstOccurrence) continue; |
| validateVariableOccurrencesUp(element, conflicts, varName, element.getTextRange().getStartOffset()); |
| } |
| } |
| return conflicts; |
| } |
| |
| private PsiElement getFirstOccurrence(boolean replaceAllOccurrences) { |
| if (replaceAllOccurrences) { |
| if (myContext.getOccurrences().length > 0) { |
| GroovyRefactoringUtil.sortOccurrences(myContext.getOccurrences()); |
| return myContext.getOccurrences()[0]; |
| } |
| else { |
| return myContext.getPlace(); |
| } |
| } |
| else { |
| final GrExpression expression = myContext.getExpression(); |
| return expression != null ? expression : myContext.getStringPart().getLiteral(); |
| } |
| } |
| |
| |
| /** |
| * Use for validator tests |
| */ |
| public String isOKTest(String varName, boolean allOccurences) { |
| MultiMap<PsiElement, String> list = isOKImpl(varName, allOccurences); |
| String result = ""; |
| final String[] strings = ArrayUtil.toStringArray((Collection<String>)list.values()); |
| Arrays.sort(strings, new Comparator<String>() { |
| @Override |
| public int compare(String o1, String o2) { |
| return o1.compareTo(o2); |
| } |
| }); |
| |
| for (String s : strings) { |
| result = result + s.replaceAll("<b><code>", "").replaceAll("</code></b>", "") + "\n"; |
| } |
| if (!list.isEmpty()) { |
| result = result.substring(0, result.length() - 1); |
| } |
| if (result.isEmpty()) { |
| result = "ok"; |
| } |
| return result; |
| } |
| |
| /** |
| * @param startElement Container to start checking conflicts from |
| * @param conflicts Conflict accumulator |
| * @param varName Variable name |
| * @param startOffset |
| */ |
| private void validateOccurrencesDown(@NotNull PsiElement startElement, |
| @NotNull MultiMap<PsiElement, String> conflicts, |
| @NotNull String varName, |
| double startOffset) { |
| PsiElement child = startElement.getFirstChild(); |
| while (child != null) { |
| // Do not check defined classes, methods, closures and blocks before |
| if (child instanceof GrTypeDefinition || |
| child instanceof GrMethod || |
| GroovyRefactoringUtil.isAppropriateContainerForIntroduceVariable(child) && |
| child.getTextRange().getEndOffset() < startOffset) { |
| myReporter.check(child, conflicts, varName); |
| child = child.getNextSibling(); |
| continue; |
| } |
| if (child instanceof GrVariable) { |
| myReporter.check(child, conflicts, varName); |
| validateOccurrencesDown(child, conflicts, varName, startOffset); |
| } |
| else { |
| validateOccurrencesDown(child, conflicts, varName, startOffset); |
| } |
| child = child.getNextSibling(); |
| } |
| } |
| |
| private void validateVariableOccurrencesUp(PsiElement startElement, |
| MultiMap<PsiElement, String> conflicts, |
| @NotNull String varName, |
| double startOffset) { |
| PsiElement prevSibling = startElement.getPrevSibling(); |
| while (prevSibling != null) { |
| if (!(GroovyRefactoringUtil.isAppropriateContainerForIntroduceVariable(prevSibling) && |
| prevSibling.getTextRange().getEndOffset() < startOffset)) { |
| validateOccurrencesDown(prevSibling, conflicts, varName, startOffset); |
| } |
| prevSibling = prevSibling.getPrevSibling(); |
| } |
| |
| PsiElement parent = startElement.getParent(); |
| // Do not check context out of method, type definition and directories |
| if (parent == null || |
| parent instanceof GrMethod || |
| parent instanceof GrTypeDefinition || |
| parent instanceof GroovyFileBase || |
| parent instanceof PsiDirectory) { |
| return; |
| } |
| |
| validateVariableOccurrencesUp(parent, conflicts, varName, startOffset); |
| } |
| |
| /** |
| * Validates name to be suggested in context |
| */ |
| @Override |
| public String validateName(String name, boolean increaseNumber) { |
| String result = name; |
| if (!isOKImpl(name, true).isEmpty() && !increaseNumber || name.isEmpty()) { |
| return ""; |
| } |
| int i = 1; |
| while (!isOKImpl(result, true).isEmpty()) { |
| result = name + i; |
| i++; |
| } |
| return result; |
| } |
| |
| @Override |
| public Project getProject() { |
| return myContext.getProject(); |
| } |
| } |