| /******************************************************************************* |
| * 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 <Integer updatedOffset, SearchMatch oldMatch> */ |
| 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 <Integer oldOffset, Integer updatedOffset> */ |
| 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; |
| } |
| } |