blob: 490efeea17348def3ee2193ad548f0c48baa2f2b [file] [log] [blame]
/*
* Copyright 2000-2009 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.rename;
import com.intellij.codeInsight.CodeInsightUtilCore;
import com.intellij.ide.actions.CopyReferenceAction;
import com.intellij.lang.Language;
import com.intellij.lang.LanguageNamesValidation;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.command.undo.BasicUndoableAction;
import com.intellij.openapi.command.undo.UndoManager;
import com.intellij.openapi.command.undo.UndoableAction;
import com.intellij.openapi.command.undo.UnexpectedUndoException;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.*;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.pom.PomTargetPsiElement;
import com.intellij.psi.*;
import com.intellij.psi.meta.PsiMetaData;
import com.intellij.psi.meta.PsiMetaOwner;
import com.intellij.psi.meta.PsiWritableMetaData;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.refactoring.RefactoringBundle;
import com.intellij.refactoring.listeners.RefactoringElementListener;
import com.intellij.refactoring.listeners.UndoRefactoringElementListener;
import com.intellij.refactoring.util.*;
import com.intellij.usageView.UsageInfo;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.containers.HashMap;
import com.intellij.util.containers.MultiMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
public class RenameUtil {
private static final Logger LOG = Logger.getInstance("#com.intellij.refactoring.rename.RenameUtil");
private RenameUtil() {
}
@NotNull
public static UsageInfo[] findUsages(final PsiElement element,
final String newName,
boolean searchInStringsAndComments,
boolean searchForTextOccurrences,
Map<? extends PsiElement, String> allRenames) {
final List<UsageInfo> result = Collections.synchronizedList(new ArrayList<UsageInfo>());
PsiManager manager = element.getManager();
GlobalSearchScope projectScope = GlobalSearchScope.projectScope(manager.getProject());
RenamePsiElementProcessor processor = RenamePsiElementProcessor.forElement(element);
Collection<PsiReference> refs = processor.findReferences(element, searchInStringsAndComments);
for (final PsiReference ref : refs) {
if (ref == null) {
LOG.error("null reference from processor " + processor);
continue;
}
PsiElement referenceElement = ref.getElement();
result.add(new MoveRenameUsageInfo(referenceElement, ref, ref.getRangeInElement().getStartOffset(),
ref.getRangeInElement().getEndOffset(), element,
ref.resolve() == null && !(ref instanceof PsiPolyVariantReference && ((PsiPolyVariantReference)ref).multiResolve(true).length > 0)));
}
processor.findCollisions(element, newName, allRenames, result);
final PsiElement searchForInComments = processor.getElementToSearchInStringsAndComments(element);
if (searchInStringsAndComments && searchForInComments != null) {
String stringToSearch = ElementDescriptionUtil.getElementDescription(searchForInComments, NonCodeSearchDescriptionLocation.STRINGS_AND_COMMENTS);
if (stringToSearch.length() > 0) {
final String stringToReplace = getStringToReplace(element, newName, false, processor);
TextOccurrencesUtil.UsageInfoFactory factory = new NonCodeUsageInfoFactory(searchForInComments, stringToReplace);
TextOccurrencesUtil.addUsagesInStringsAndComments(searchForInComments, stringToSearch, result, factory);
}
}
if (searchForTextOccurrences && searchForInComments != null) {
String stringToSearch = ElementDescriptionUtil.getElementDescription(searchForInComments, NonCodeSearchDescriptionLocation.NON_JAVA);
if (stringToSearch.length() > 0) {
final String stringToReplace = getStringToReplace(element, newName, true, processor);
addTextOccurrence(searchForInComments, result, projectScope, stringToSearch, stringToReplace);
}
final Pair<String, String> additionalStringToSearch = processor.getTextOccurrenceSearchStrings(searchForInComments, newName);
if (additionalStringToSearch != null && additionalStringToSearch.first.length() > 0) {
addTextOccurrence(searchForInComments, result, projectScope, additionalStringToSearch.first, additionalStringToSearch.second);
}
}
return result.toArray(new UsageInfo[result.size()]);
}
private static void addTextOccurrence(final PsiElement element, final List<UsageInfo> result, final GlobalSearchScope projectScope,
final String stringToSearch, final String stringToReplace) {
TextOccurrencesUtil.UsageInfoFactory factory = new TextOccurrencesUtil.UsageInfoFactory() {
@Override
public UsageInfo createUsageInfo(@NotNull PsiElement usage, int startOffset, int endOffset) {
TextRange textRange = usage.getTextRange();
int start = textRange == null ? 0 : textRange.getStartOffset();
return NonCodeUsageInfo.create(usage.getContainingFile(), start + startOffset, start + endOffset, element, stringToReplace);
}
};
TextOccurrencesUtil.addTextOccurences(element, stringToSearch, projectScope, result, factory);
}
public static void buildPackagePrefixChangedMessage(final VirtualFile[] virtualFiles, StringBuffer message, final String qualifiedName) {
if (virtualFiles.length > 0) {
message.append(RefactoringBundle.message("package.occurs.in.package.prefixes.of.the.following.source.folders.n", qualifiedName));
for (final VirtualFile virtualFile : virtualFiles) {
message.append(virtualFile.getPresentableUrl()).append("\n");
}
message.append(RefactoringBundle.message("these.package.prefixes.will.be.changed"));
}
}
private static String getStringToReplace(PsiElement element, String newName, boolean nonJava, final RenamePsiElementProcessor theProcessor) {
if (element instanceof PsiMetaOwner) {
final PsiMetaOwner psiMetaOwner = (PsiMetaOwner)element;
final PsiMetaData metaData = psiMetaOwner.getMetaData();
if (metaData != null) {
return metaData.getName();
}
}
if (theProcessor != null) {
String result = theProcessor.getQualifiedNameAfterRename(element, newName, nonJava);
if (result != null) {
return result;
}
}
if (element instanceof PsiNamedElement) {
return newName;
}
else {
LOG.error("Unknown element type");
return null;
}
}
public static void checkRename(PsiElement element, String newName) throws IncorrectOperationException {
if (element instanceof PsiCheckedRenameElement) {
((PsiCheckedRenameElement)element).checkSetName(newName);
}
}
public static void doRename(final PsiElement element, String newName, UsageInfo[] usages, final Project project,
@Nullable final RefactoringElementListener listener) throws IncorrectOperationException{
final RenamePsiElementProcessor processor = RenamePsiElementProcessor.forElement(element);
final String fqn = element instanceof PsiFile ? ((PsiFile)element).getVirtualFile().getPath() : CopyReferenceAction.elementToFqn(element);
if (fqn != null) {
UndoableAction action = new BasicUndoableAction() {
@Override
public void undo() throws UnexpectedUndoException {
if (listener instanceof UndoRefactoringElementListener) {
((UndoRefactoringElementListener)listener).undoElementMovedOrRenamed(element, fqn);
}
}
@Override
public void redo() throws UnexpectedUndoException {
}
};
UndoManager.getInstance(project).undoableActionPerformed(action);
}
processor.renameElement(element, newName, usages, listener);
}
public static void showErrorMessage(final IncorrectOperationException e, final PsiElement element, final Project project) {
// may happen if the file or package cannot be renamed. e.g. locked by another application
if (ApplicationManager.getApplication().isUnitTestMode()) {
throw new RuntimeException(e);
//LOG.error(e);
//return;
}
ApplicationManager.getApplication().invokeLater(new Runnable() {
@Override
public void run() {
final String helpID = RenamePsiElementProcessor.forElement(element).getHelpID(element);
String message = e.getMessage();
if (StringUtil.isEmpty(message)) {
message = RefactoringBundle.message("rename.not.supported");
}
CommonRefactoringUtil.showErrorMessage(RefactoringBundle.message("rename.title"), message, helpID, project);
}
});
}
public static void doRenameGenericNamedElement(@NotNull PsiElement namedElement, String newName, UsageInfo[] usages,
@Nullable RefactoringElementListener listener) throws IncorrectOperationException {
PsiWritableMetaData writableMetaData = null;
if (namedElement instanceof PsiMetaOwner) {
final PsiMetaData metaData = ((PsiMetaOwner)namedElement).getMetaData();
if (metaData instanceof PsiWritableMetaData) {
writableMetaData = (PsiWritableMetaData)metaData;
}
}
if (writableMetaData == null && !(namedElement instanceof PsiNamedElement)) {
LOG.error("Unknown element type:" + namedElement);
}
boolean hasBindables = false;
for (UsageInfo usage : usages) {
if (!(usage.getReference() instanceof BindablePsiReference)) {
rename(usage, newName);
} else {
hasBindables = true;
}
}
if (writableMetaData != null) {
writableMetaData.setName(newName);
}
else {
PsiElement namedElementAfterRename = ((PsiNamedElement)namedElement).setName(newName);
if (namedElementAfterRename != null) namedElement = namedElementAfterRename;
}
if (hasBindables) {
for (UsageInfo usage : usages) {
final PsiReference ref = usage.getReference();
if (ref instanceof BindablePsiReference) {
boolean fallback = true;
if (!(ref instanceof FragmentaryPsiReference
&& ((FragmentaryPsiReference)ref).isFragmentOnlyRename())) {
try {
ref.bindToElement(namedElement);
fallback = false;
}
catch (IncorrectOperationException ignored) {
}
}
if (fallback) {//fall back to old scheme
ref.handleElementRename(newName);
}
}
}
}
if (listener != null) {
listener.elementRenamed(namedElement);
}
}
public static void rename(UsageInfo info, String newName) throws IncorrectOperationException {
if (info.getElement() == null) return;
PsiReference ref = info.getReference();
if (ref == null) return;
ref.handleElementRename(newName);
}
@Nullable
public static List<UnresolvableCollisionUsageInfo> removeConflictUsages(Set<UsageInfo> usages) {
final List<UnresolvableCollisionUsageInfo> result = new ArrayList<UnresolvableCollisionUsageInfo>();
for (Iterator<UsageInfo> iterator = usages.iterator(); iterator.hasNext();) {
UsageInfo usageInfo = iterator.next();
if (usageInfo instanceof UnresolvableCollisionUsageInfo) {
result.add((UnresolvableCollisionUsageInfo)usageInfo);
iterator.remove();
}
}
return result.isEmpty() ? null : result;
}
public static void addConflictDescriptions(UsageInfo[] usages, MultiMap<PsiElement, String> conflicts) {
for (UsageInfo usage : usages) {
if (usage instanceof UnresolvableCollisionUsageInfo) {
conflicts.putValue(usage.getElement(), ((UnresolvableCollisionUsageInfo)usage).getDescription());
}
}
}
public static void renameNonCodeUsages(@NotNull Project project, @NotNull NonCodeUsageInfo[] usages) {
PsiDocumentManager.getInstance(project).commitAllDocuments();
Map<Document, List<UsageOffset>> docsToOffsetsMap = new HashMap<Document, List<UsageOffset>>();
final PsiDocumentManager psiDocumentManager = PsiDocumentManager.getInstance(project);
for (NonCodeUsageInfo usage : usages) {
PsiElement element = usage.getElement();
if (element == null) continue;
element = CodeInsightUtilCore.forcePsiPostprocessAndRestoreElement(element, true);
if (element == null) continue;
final ProperTextRange rangeInElement = usage.getRangeInElement();
if (rangeInElement == null) continue;
final PsiFile containingFile = element.getContainingFile();
final Document document = psiDocumentManager.getDocument(containingFile);
final Segment segment = usage.getSegment();
LOG.assertTrue(segment != null);
int fileOffset = segment.getStartOffset();
List<UsageOffset> list = docsToOffsetsMap.get(document);
if (list == null) {
list = new ArrayList<UsageOffset>();
docsToOffsetsMap.put(document, list);
}
list.add(new UsageOffset(fileOffset, fileOffset + rangeInElement.getLength(), usage.newText));
}
for (Document document : docsToOffsetsMap.keySet()) {
List<UsageOffset> list = docsToOffsetsMap.get(document);
LOG.assertTrue(list != null, document);
UsageOffset[] offsets = list.toArray(new UsageOffset[list.size()]);
Arrays.sort(offsets);
for (int i = offsets.length - 1; i >= 0; i--) {
UsageOffset usageOffset = offsets[i];
document.replaceString(usageOffset.startOffset, usageOffset.endOffset, usageOffset.newText);
}
PsiDocumentManager.getInstance(project).commitDocument(document);
}
PsiDocumentManager.getInstance(project).commitAllDocuments();
}
public static boolean isValidName(final Project project, final PsiElement psiElement, final String newName) {
if (newName == null || newName.length() == 0) {
return false;
}
final Condition<String> inputValidator = RenameInputValidatorRegistry.getInputValidator(psiElement);
if (inputValidator != null) {
return inputValidator.value(newName);
}
if (psiElement instanceof PsiFile || psiElement instanceof PsiDirectory) {
return newName.indexOf('\\') < 0 && newName.indexOf('/') < 0;
}
if (psiElement instanceof PomTargetPsiElement) {
return !StringUtil.isEmptyOrSpaces(newName);
}
final PsiFile file = psiElement.getContainingFile();
final Language elementLanguage = psiElement.getLanguage();
final Language fileLanguage = file == null ? null : file.getLanguage();
Language language = fileLanguage == null ? elementLanguage : fileLanguage.isKindOf(elementLanguage) ? fileLanguage : elementLanguage;
return LanguageNamesValidation.INSTANCE.forLanguage(language).isIdentifier(newName.trim(), project);
}
private static class UsageOffset implements Comparable<UsageOffset> {
final int startOffset;
final int endOffset;
final String newText;
public UsageOffset(int startOffset, int endOffset, String newText) {
this.startOffset = startOffset;
this.endOffset = endOffset;
this.newText = newText;
}
@Override
public int compareTo(final UsageOffset o) {
return startOffset - o.startOffset;
}
}
}