blob: cbeefc786612cf7a05af4199dd75952273bfd702 [file] [log] [blame]
/*
* Copyright 2000-2012 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.inline;
import com.intellij.codeInsight.TargetElementUtilBase;
import com.intellij.lang.Language;
import com.intellij.lang.refactoring.InlineHandler;
import com.intellij.lang.refactoring.InlineHandlers;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.command.CommandProcessor;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiNamedElement;
import com.intellij.psi.PsiReference;
import com.intellij.psi.search.searches.ReferencesSearch;
import com.intellij.refactoring.BaseRefactoringProcessor;
import com.intellij.refactoring.RefactoringBundle;
import com.intellij.refactoring.ui.ConflictsDialog;
import com.intellij.refactoring.util.CommonRefactoringUtil;
import com.intellij.usageView.UsageInfo;
import com.intellij.util.containers.HashMap;
import com.intellij.util.containers.HashSet;
import com.intellij.util.containers.MultiMap;
import org.jetbrains.annotations.Nullable;
import java.util.*;
/**
* @author ven
*/
@SuppressWarnings({"UtilityClassWithoutPrivateConstructor"})
public class GenericInlineHandler {
private static final Logger LOG = Logger.getInstance(GenericInlineHandler.class);
public static boolean invoke(final PsiElement element, @Nullable Editor editor, final InlineHandler languageSpecific) {
final PsiReference invocationReference = editor != null ? TargetElementUtilBase.findReference(editor) : null;
final InlineHandler.Settings settings = languageSpecific.prepareInlineElement(element, editor, invocationReference != null);
if (settings == null || settings == InlineHandler.Settings.CANNOT_INLINE_SETTINGS) {
return settings != null;
}
final Collection<? extends PsiReference> allReferences;
if (settings.isOnlyOneReferenceToInline()) {
allReferences = Collections.singleton(invocationReference);
}
else {
final Ref<Collection<? extends PsiReference>> usagesRef = new Ref<Collection<? extends PsiReference>>();
ProgressManager.getInstance().runProcessWithProgressSynchronously(new Runnable() {
@Override
public void run() {
usagesRef.set(ReferencesSearch.search(element).findAll());
}
}, "Find Usages", false, element.getProject());
allReferences = usagesRef.get();
}
final MultiMap<PsiElement, String> conflicts = new MultiMap<PsiElement, String>();
final Map<Language, InlineHandler.Inliner> inliners = initializeInliners(element, settings, allReferences);
for (PsiReference reference : allReferences) {
collectConflicts(reference, element, inliners, conflicts);
}
final Project project = element.getProject();
if (!conflicts.isEmpty()) {
if (ApplicationManager.getApplication().isUnitTestMode()) {
throw new BaseRefactoringProcessor.ConflictsInTestsException(conflicts.values());
}
else {
final ConflictsDialog conflictsDialog = new ConflictsDialog(project, conflicts);
conflictsDialog.show();
if (!conflictsDialog.isOK()) {
return true;
}
}
}
HashSet<PsiElement> elements = new HashSet<PsiElement>();
for (PsiReference reference : allReferences) {
PsiElement refElement = reference.getElement();
if (refElement != null) {
elements.add(refElement);
}
}
if (!settings.isOnlyOneReferenceToInline()) {
elements.add(element);
}
if (!CommonRefactoringUtil.checkReadOnlyStatusRecursively(project, elements, true)) {
return true;
}
ApplicationManager.getApplication().runWriteAction(new Runnable() {
@Override
public void run() {
final String subj = element instanceof PsiNamedElement ? ((PsiNamedElement)element).getName() : "element";
CommandProcessor.getInstance().executeCommand(project, new Runnable() {
@Override
public void run() {
final PsiReference[] references = sortDepthFirstRightLeftOrder(allReferences);
final UsageInfo[] usages = new UsageInfo[references.length];
for (int i = 0; i < references.length; i++) {
usages[i] = new UsageInfo(references[i]);
}
for (UsageInfo usage : usages) {
inlineReference(usage, element, inliners);
}
if (!settings.isOnlyOneReferenceToInline()) {
languageSpecific.removeDefinition(element, settings);
}
}
}, RefactoringBundle.message("inline.command", StringUtil.notNullize(subj, "<nameless>")), null);
}
});
return true;
}
public static Map<Language, InlineHandler.Inliner> initializeInliners(PsiElement element,
InlineHandler.Settings settings,
Collection<? extends PsiReference> allReferences) {
final Map<Language, InlineHandler.Inliner> inliners = new HashMap<Language, InlineHandler.Inliner>();
for (PsiReference ref : allReferences) {
if (ref == null) {
LOG.error("element: " + element.getClass()+ ", allReferences contains null!");
continue;
}
PsiElement refElement = ref.getElement();
LOG.assertTrue(refElement != null, ref.getClass().getName());
final Language language = refElement.getLanguage();
if (inliners.containsKey(language)) continue;
final List<InlineHandler> handlers = InlineHandlers.getInlineHandlers(language);
for (InlineHandler handler : handlers) {
InlineHandler.Inliner inliner = handler.createInliner(element, settings);
if (inliner != null) {
inliners.put(language, inliner);
break;
}
}
}
return inliners;
}
public static void collectConflicts(final PsiReference reference,
final PsiElement element,
final Map<Language, InlineHandler.Inliner> inliners,
final MultiMap<PsiElement, String> conflicts) {
final PsiElement referenceElement = reference.getElement();
if (referenceElement == null) return;
final Language language = referenceElement.getLanguage();
final InlineHandler.Inliner inliner = inliners.get(language);
if (inliner != null) {
final MultiMap<PsiElement, String> refConflicts = inliner.getConflicts(reference, element);
if (refConflicts != null) {
for (PsiElement psiElement : refConflicts.keySet()) {
conflicts.putValues(psiElement, refConflicts.get(psiElement));
}
}
}
else {
conflicts.putValue(referenceElement, "Cannot inline reference from " + language.getDisplayName());
}
}
public static void inlineReference(final UsageInfo usage,
final PsiElement element,
final Map<Language, InlineHandler.Inliner> inliners) {
PsiElement usageElement = usage.getElement();
if (usageElement == null) return;
final Language language = usageElement.getLanguage();
final InlineHandler.Inliner inliner = inliners.get(language);
if (inliner != null) {
inliner.inlineUsage(usage, element);
}
}
//order of usages across different files is irrelevant
public static PsiReference[] sortDepthFirstRightLeftOrder(final Collection<? extends PsiReference> allReferences) {
final PsiReference[] usages = allReferences.toArray(new PsiReference[allReferences.size()]);
Arrays.sort(usages, new Comparator<PsiReference>() {
@Override
public int compare(final PsiReference usage1, final PsiReference usage2) {
final PsiElement element1 = usage1.getElement();
final PsiElement element2 = usage2.getElement();
if (element1 == null || element2 == null) return 0;
return element2.getTextRange().getStartOffset() - element1.getTextRange().getStartOffset();
}
});
return usages;
}
}