blob: 9ace083b213174a0a94d27549f80b9cf5c14f0ab [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2006 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.jdt.internal.corext.refactoring.rename;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import org.eclipse.text.edits.TextEdit;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.core.resources.IResource;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.Region;
import org.eclipse.ltk.core.refactoring.RefactoringStatus;
import org.eclipse.ltk.core.refactoring.RefactoringStatusContext;
import org.eclipse.ltk.core.refactoring.RefactoringStatusEntry;
import org.eclipse.ltk.core.refactoring.TextChange;
import org.eclipse.ltk.core.refactoring.TextEditChangeGroup;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.ISourceRange;
import org.eclipse.jdt.core.ISourceReference;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.WorkingCopyOwner;
import org.eclipse.jdt.core.compiler.IProblem;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.IBinding;
import org.eclipse.jdt.core.dom.IVariableBinding;
import org.eclipse.jdt.core.dom.Name;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.VariableDeclaration;
import org.eclipse.jdt.core.search.FieldDeclarationMatch;
import org.eclipse.jdt.core.search.MethodDeclarationMatch;
import org.eclipse.jdt.core.search.SearchMatch;
import org.eclipse.jdt.internal.corext.SourceRange;
import org.eclipse.jdt.internal.corext.dom.ASTNodes;
import org.eclipse.jdt.internal.corext.dom.NodeFinder;
import org.eclipse.jdt.internal.corext.refactoring.RefactoringCoreMessages;
import org.eclipse.jdt.internal.corext.refactoring.SearchResultGroup;
import org.eclipse.jdt.internal.corext.refactoring.base.JavaStatusContext;
import org.eclipse.jdt.internal.corext.refactoring.base.JavaStringStatusContext;
import org.eclipse.jdt.internal.corext.refactoring.util.RefactoringASTParser;
import org.eclipse.jdt.internal.corext.refactoring.util.TextChangeManager;
import org.eclipse.jdt.internal.corext.util.Messages;
import org.eclipse.jdt.internal.corext.util.SearchUtils;
class RenameAnalyzeUtil {
private static class ProblemNodeFinder {
private ProblemNodeFinder() {
//static
}
public static SimpleName[] getProblemNodes(ASTNode methodNode, VariableDeclaration variableNode, TextEdit[] edits, TextChange change) {
String key= variableNode.resolveBinding().getKey();
NameNodeVisitor visitor= new NameNodeVisitor(edits, change, key);
methodNode.accept(visitor);
return visitor.getProblemNodes();
}
private static class NameNodeVisitor extends ASTVisitor {
private Collection fRanges;
private Collection fProblemNodes;
private String fKey;
public NameNodeVisitor(TextEdit[] edits, TextChange change, String key) {
Assert.isNotNull(edits);
Assert.isNotNull(key);
fRanges= new HashSet(Arrays.asList(RefactoringAnalyzeUtil.getNewRanges(edits, change)));
fProblemNodes= new ArrayList(0);
fKey= key;
}
public SimpleName[] getProblemNodes() {
return (SimpleName[]) fProblemNodes.toArray(new SimpleName[fProblemNodes.size()]);
}
//----- visit methods
public boolean visit(SimpleName node) {
VariableDeclaration decl= getVariableDeclaration(node);
if (decl == null)
return super.visit(node);
IVariableBinding binding= decl.resolveBinding();
if (binding == null)
return super.visit(node);
boolean keysEqual= fKey.equals(binding.getKey());
boolean rangeInSet= fRanges.contains(new Region(node.getStartPosition(), node.getLength()));
if (keysEqual && !rangeInSet)
fProblemNodes.add(node);
if (!keysEqual && rangeInSet)
fProblemNodes.add(node);
/*
* if (!keyEquals && !rangeInSet)
* ok, different local variable.
*
* if (keyEquals && rangeInSet)
* ok, renamed local variable & has been renamed.
*/
return super.visit(node);
}
}
}
static class LocalAnalyzePackage {
public final TextEdit fDeclarationEdit;
public final TextEdit[] fOccurenceEdits;
public LocalAnalyzePackage(final TextEdit declarationEdit, final TextEdit[] occurenceEdits) {
fDeclarationEdit = declarationEdit;
fOccurenceEdits = occurenceEdits;
}
}
private RenameAnalyzeUtil() {
//no instance
}
static RefactoringStatus analyzeRenameChanges(TextChangeManager manager, SearchResultGroup[] oldOccurrences, SearchResultGroup[] newOccurrences) {
RefactoringStatus result= new RefactoringStatus();
for (int i= 0; i < oldOccurrences.length; i++) {
SearchResultGroup oldGroup= oldOccurrences[i];
SearchMatch[] oldSearchResults= oldGroup.getSearchResults();
ICompilationUnit cunit= oldGroup.getCompilationUnit();
if (cunit == null)
continue;
for (int j= 0; j < oldSearchResults.length; j++) {
SearchMatch oldSearchResult= oldSearchResults[j];
if (! RenameAnalyzeUtil.existsInNewOccurrences(oldSearchResult, newOccurrences, manager)){
addShadowsError(cunit, oldSearchResult, result);
}
}
}
return result;
}
static ICompilationUnit findWorkingCopyForCu(ICompilationUnit[] newWorkingCopies, ICompilationUnit cu){
ICompilationUnit original= cu == null ? null : cu.getPrimary();
for (int i= 0; i < newWorkingCopies.length; i++) {
if (newWorkingCopies[i].getPrimary().equals(original))
return newWorkingCopies[i];
}
return null;
}
static ICompilationUnit[] createNewWorkingCopies(ICompilationUnit[] compilationUnitsToModify, TextChangeManager manager, WorkingCopyOwner owner, SubProgressMonitor pm) throws CoreException {
pm.beginTask("", compilationUnitsToModify.length); //$NON-NLS-1$
ICompilationUnit[] newWorkingCopies= new ICompilationUnit[compilationUnitsToModify.length];
for (int i= 0; i < compilationUnitsToModify.length; i++) {
ICompilationUnit cu= compilationUnitsToModify[i];
newWorkingCopies[i]= createNewWorkingCopy(cu, manager, owner, new SubProgressMonitor(pm, 1));
}
pm.done();
return newWorkingCopies;
}
static ICompilationUnit createNewWorkingCopy(ICompilationUnit cu, TextChangeManager manager,
WorkingCopyOwner owner, SubProgressMonitor pm) throws CoreException {
ICompilationUnit newWc= cu.getWorkingCopy(owner, null, null);
String previewContent= manager.get(cu).getPreviewContent(new NullProgressMonitor());
newWc.getBuffer().setContents(previewContent);
newWc.reconcile(ICompilationUnit.NO_AST, false, owner, pm);
return newWc;
}
private static boolean existsInNewOccurrences(SearchMatch searchResult, SearchResultGroup[] newOccurrences, TextChangeManager manager) {
SearchResultGroup newGroup= findOccurrenceGroup(searchResult.getResource(), newOccurrences);
if (newGroup == null)
return false;
IRegion oldEditRange= getCorrespondingEditChangeRange(searchResult, manager);
if (oldEditRange == null)
return false;
SearchMatch[] newSearchResults= newGroup.getSearchResults();
int oldRangeOffset = oldEditRange.getOffset();
for (int i= 0; i < newSearchResults.length; i++) {
if (newSearchResults[i].getOffset() == oldRangeOffset)
return true;
}
return false;
}
private static IRegion getCorrespondingEditChangeRange(SearchMatch searchResult, TextChangeManager manager) {
TextChange change= getTextChange(searchResult, manager);
if (change == null)
return null;
IRegion oldMatchRange= createTextRange(searchResult);
TextEditChangeGroup[] editChanges= change.getTextEditChangeGroups();
for (int i= 0; i < editChanges.length; i++) {
if (oldMatchRange.equals(editChanges[i].getRegion()))
return TextEdit.getCoverage(change.getPreviewEdits(editChanges[i].getTextEdits()));
}
return null;
}
private static TextChange getTextChange(SearchMatch searchResult, TextChangeManager manager) {
ICompilationUnit cu= SearchUtils.getCompilationUnit(searchResult);
if (cu == null)
return null;
return manager.get(cu);
}
private static IRegion createTextRange(SearchMatch searchResult) {
return new Region(searchResult.getOffset(), searchResult.getLength());
}
private static SearchResultGroup findOccurrenceGroup(IResource resource, SearchResultGroup[] newOccurrences) {
for (int i= 0; i < newOccurrences.length; i++) {
if (newOccurrences[i].getResource().equals(resource))
return newOccurrences[i];
}
return null;
}
//--- find missing changes in BOTH directions
//TODO: Currently filters out declarations (MethodDeclarationMatch, FieldDeclarationMatch).
//Long term solution: only pass reference search results in.
static RefactoringStatus analyzeRenameChanges2(TextChangeManager manager,
SearchResultGroup[] oldReferences, SearchResultGroup[] newReferences, String newElementName) {
RefactoringStatus result= new RefactoringStatus();
HashMap cuToNewResults= new HashMap(newReferences.length);
for (int i1= 0; i1 < newReferences.length; i1++) {
ICompilationUnit cu= newReferences[i1].getCompilationUnit();
if (cu != null)
cuToNewResults.put(cu.getPrimary(), newReferences[i1].getSearchResults());
}
for (int i= 0; i < oldReferences.length; i++) {
SearchResultGroup oldGroup= oldReferences[i];
SearchMatch[] oldMatches= oldGroup.getSearchResults();
ICompilationUnit cu= oldGroup.getCompilationUnit();
if (cu == null)
continue;
SearchMatch[] newSearchMatches= (SearchMatch[]) cuToNewResults.remove(cu);
if (newSearchMatches == null) {
for (int j = 0; j < oldMatches.length; j++) {
SearchMatch oldMatch = oldMatches[j];
addShadowsError(cu, oldMatch, result);
}
} else {
analyzeChanges(cu, manager.get(cu), oldMatches, newSearchMatches, newElementName, result);
}
}
for (Iterator iter= cuToNewResults.entrySet().iterator(); iter.hasNext();) {
Map.Entry entry= (Entry) iter.next();
ICompilationUnit cu= (ICompilationUnit) entry.getKey();
SearchMatch[] newSearchMatches= (SearchMatch[]) entry.getValue();
for (int i= 0; i < newSearchMatches.length; i++) {
SearchMatch newMatch= newSearchMatches[i];
addReferenceShadowedError(cu, newMatch, newElementName, result);
}
}
return result;
}
private static void analyzeChanges(ICompilationUnit cu, TextChange change,
SearchMatch[] oldMatches, SearchMatch[] newMatches, String newElementName, RefactoringStatus result) {
Map updatedOldOffsets= getUpdatedChangeOffsets(change, oldMatches);
for (int i= 0; i < newMatches.length; i++) {
SearchMatch newMatch= newMatches[i];
Integer offsetInNew= new Integer(newMatch.getOffset());
SearchMatch oldMatch= (SearchMatch) updatedOldOffsets.remove(offsetInNew);
if (oldMatch == null) {
addReferenceShadowedError(cu, newMatch, newElementName, result);
}
}
for (Iterator iter= updatedOldOffsets.values().iterator(); iter.hasNext();) {
// remaining old matches are not found any more -> they have been shadowed
SearchMatch oldMatch= (SearchMatch) iter.next();
addShadowsError(cu, oldMatch, result);
}
}
/** @return Map &lt;Integer updatedOffset, SearchMatch oldMatch&gt; */
private static Map getUpdatedChangeOffsets(TextChange change, SearchMatch[] oldMatches) {
Map/*<Integer updatedOffset, SearchMatch oldMatch>*/ updatedOffsets= new HashMap();
Map oldToUpdatedOffsets= getEditChangeOffsetUpdates(change);
for (int i= 0; i < oldMatches.length; i++) {
SearchMatch oldMatch= oldMatches[i];
Integer updatedOffset= (Integer) oldToUpdatedOffsets.get(new Integer(oldMatch.getOffset()));
if (updatedOffset == null)
updatedOffset= new Integer(-1); //match not updated
updatedOffsets.put(updatedOffset, oldMatch);
}
return updatedOffsets;
}
/** @return Map &lt;Integer oldOffset, Integer updatedOffset&gt; */
private static Map getEditChangeOffsetUpdates(TextChange change) {
TextEditChangeGroup[] editChanges= change.getTextEditChangeGroups();
Map/*<oldOffset, newOffset>*/ offsetUpdates= new HashMap(editChanges.length);
for (int i= 0; i < editChanges.length; i++) {
TextEditChangeGroup editChange= editChanges[i];
IRegion oldRegion= editChange.getRegion();
if (oldRegion == null)
continue;
IRegion updatedRegion= TextEdit.getCoverage(change.getPreviewEdits(editChange.getTextEdits()));
if (updatedRegion == null)
continue;
offsetUpdates.put(new Integer(oldRegion.getOffset()), new Integer(updatedRegion.getOffset()));
}
return offsetUpdates;
}
private static void addReferenceShadowedError(ICompilationUnit cu, SearchMatch newMatch, String newElementName, RefactoringStatus result) {
//Found a new match with no corresponding old match.
//-> The new match is a reference which was pointing to another element,
//but that other element has been shadowed
//TODO: should not have to filter declarations:
if (newMatch instanceof MethodDeclarationMatch || newMatch instanceof FieldDeclarationMatch)
return;
ISourceRange range= getOldSourceRange(newMatch);
RefactoringStatusContext context= JavaStatusContext.create(cu, range);
String message= Messages.format(
RefactoringCoreMessages.RenameAnalyzeUtil_reference_shadowed,
new String[] {cu.getElementName(), newElementName});
result.addError(message, context);
}
private static ISourceRange getOldSourceRange(SearchMatch newMatch) {
// cannot transfom offset in preview to offset in original -> just show enclosing method
IJavaElement newMatchElement= (IJavaElement) newMatch.getElement();
IJavaElement primaryElement= newMatchElement.getPrimaryElement();
ISourceRange range= null;
if (primaryElement.exists() && primaryElement instanceof ISourceReference) {
try {
range= ((ISourceReference) primaryElement).getSourceRange();
} catch (JavaModelException e) {
// can live without source range
}
}
return range;
}
private static void addShadowsError(ICompilationUnit cu, SearchMatch oldMatch, RefactoringStatus result) {
// Old match not found in new matches -> reference has been shadowed
//TODO: should not have to filter declarations:
if (oldMatch instanceof MethodDeclarationMatch || oldMatch instanceof FieldDeclarationMatch)
return;
ISourceRange range= new SourceRange(oldMatch.getOffset(), oldMatch.getLength());
RefactoringStatusContext context= JavaStatusContext.create(cu, range);
String message= Messages.format(RefactoringCoreMessages.RenameAnalyzeUtil_shadows, cu.getElementName());
result.addError(message, context);
}
/**
* This method analyzes a set of local variable renames inside one cu. It checks whether
* any new compile errors have been introduced by the rename(s) and whether the correct
* node(s) has/have been renamed.
*
* @param analyzePackages the LocalAnalyzePackages containing the information about the local renames
* @param cuChange the TextChange containing all local variable changes to be applied.
* @param oldCUNode the fully (incl. bindings) resolved AST node of the original compilation unit
* @param statementsRecovery whether statements recovery should be performed when parsing the changed CU
* @return a RefactoringStatus containing errors if compile errors or wrongly renamed nodes are found
* @throws CoreException thrown if there was an error greating the preview content of the change
*/
public static RefactoringStatus analyzeLocalRenames(LocalAnalyzePackage[] analyzePackages, TextChange cuChange, CompilationUnit oldCUNode, boolean statementsRecovery) throws CoreException {
RefactoringStatus result= new RefactoringStatus();
ICompilationUnit compilationUnit= (ICompilationUnit) oldCUNode.getJavaElement();
String newCuSource= cuChange.getPreviewContent(new NullProgressMonitor());
CompilationUnit newCUNode= new RefactoringASTParser(AST.JLS3).parse(newCuSource, compilationUnit, true, statementsRecovery, null);
result.merge(analyzeCompileErrors(newCuSource, newCUNode, oldCUNode));
if (result.hasError())
return result;
for (int i= 0; i < analyzePackages.length; i++) {
ASTNode enclosing= getEnclosingBlockOrMethod(analyzePackages[i].fDeclarationEdit, cuChange, newCUNode);
// get new declaration
IRegion newRegion= RefactoringAnalyzeUtil.getNewTextRange(analyzePackages[i].fDeclarationEdit, cuChange);
ASTNode newDeclaration= NodeFinder.perform(newCUNode, newRegion.getOffset(), newRegion.getLength());
Assert.isTrue(newDeclaration instanceof Name);
VariableDeclaration declaration= getVariableDeclaration((Name) newDeclaration);
Assert.isNotNull(declaration);
SimpleName[] problemNodes= ProblemNodeFinder.getProblemNodes(enclosing, declaration, analyzePackages[i].fOccurenceEdits, cuChange);
result.merge(RefactoringAnalyzeUtil.reportProblemNodes(newCuSource, problemNodes));
}
return result;
}
private static VariableDeclaration getVariableDeclaration(Name node) {
IBinding binding= node.resolveBinding();
if (binding == null && node.getParent() instanceof VariableDeclaration)
return (VariableDeclaration) node.getParent();
if (binding != null && binding.getKind() == IBinding.VARIABLE) {
CompilationUnit cu= (CompilationUnit) ASTNodes.getParent(node, CompilationUnit.class);
return ASTNodes.findVariableDeclaration( ((IVariableBinding) binding), cu);
}
return null;
}
private static ASTNode getEnclosingBlockOrMethod(TextEdit declarationEdit, TextChange change, CompilationUnit newCUNode) {
ASTNode enclosing= RefactoringAnalyzeUtil.getBlock(declarationEdit, change, newCUNode);
if (enclosing == null)
enclosing= RefactoringAnalyzeUtil.getMethodDeclaration(declarationEdit, change, newCUNode);
return enclosing;
}
private static RefactoringStatus analyzeCompileErrors(String newCuSource, CompilationUnit newCUNode, CompilationUnit oldCUNode) {
RefactoringStatus result= new RefactoringStatus();
IProblem[] newProblems= RefactoringAnalyzeUtil.getIntroducedCompileProblems(newCUNode, oldCUNode);
for (int i= 0; i < newProblems.length; i++) {
IProblem problem= newProblems[i];
if (problem.isError())
result.addEntry(new RefactoringStatusEntry((problem.isError() ? RefactoringStatus.ERROR : RefactoringStatus.WARNING), problem.getMessage(), new JavaStringStatusContext(newCuSource, new SourceRange(problem))));
}
return result;
}
}