]> git.uio.no Git - ifi-stolz-refaktor.git/blob - case-study/jdt-before/core refactoring/org/eclipse/jdt/internal/corext/refactoring/rename/RenameAnalyzeUtil.java
Case Study: adding data and statistics
[ifi-stolz-refaktor.git] / case-study / jdt-before / core refactoring / org / eclipse / jdt / internal / corext / refactoring / rename / RenameAnalyzeUtil.java
1 /*******************************************************************************
2  * Copyright (c) 2000, 2011 IBM Corporation and others.
3  * All rights reserved. This program and the accompanying materials
4  * are made available under the terms of the Eclipse Public License v1.0
5  * which accompanies this distribution, and is available at
6  * http://www.eclipse.org/legal/epl-v10.html
7  *
8  * Contributors:
9  *     IBM Corporation - initial API and implementation
10  *******************************************************************************/
11 package org.eclipse.jdt.internal.corext.refactoring.rename;
12
13 import java.util.ArrayList;
14 import java.util.Arrays;
15 import java.util.Collection;
16 import java.util.HashMap;
17 import java.util.HashSet;
18 import java.util.Iterator;
19 import java.util.Map;
20 import java.util.Map.Entry;
21
22 import org.eclipse.core.runtime.Assert;
23 import org.eclipse.core.runtime.CoreException;
24 import org.eclipse.core.runtime.NullProgressMonitor;
25 import org.eclipse.core.runtime.SubProgressMonitor;
26
27 import org.eclipse.core.resources.IResource;
28
29 import org.eclipse.text.edits.TextEdit;
30
31 import org.eclipse.jface.text.IRegion;
32 import org.eclipse.jface.text.Region;
33
34 import org.eclipse.ltk.core.refactoring.RefactoringStatus;
35 import org.eclipse.ltk.core.refactoring.RefactoringStatusContext;
36 import org.eclipse.ltk.core.refactoring.RefactoringStatusEntry;
37 import org.eclipse.ltk.core.refactoring.TextChange;
38 import org.eclipse.ltk.core.refactoring.TextEditChangeGroup;
39
40 import org.eclipse.jdt.core.ICompilationUnit;
41 import org.eclipse.jdt.core.IJavaElement;
42 import org.eclipse.jdt.core.ISourceRange;
43 import org.eclipse.jdt.core.ISourceReference;
44 import org.eclipse.jdt.core.JavaModelException;
45 import org.eclipse.jdt.core.SourceRange;
46 import org.eclipse.jdt.core.WorkingCopyOwner;
47 import org.eclipse.jdt.core.compiler.IProblem;
48 import org.eclipse.jdt.core.dom.ASTNode;
49 import org.eclipse.jdt.core.dom.ASTVisitor;
50 import org.eclipse.jdt.core.dom.CompilationUnit;
51 import org.eclipse.jdt.core.dom.IBinding;
52 import org.eclipse.jdt.core.dom.IVariableBinding;
53 import org.eclipse.jdt.core.dom.Name;
54 import org.eclipse.jdt.core.dom.NodeFinder;
55 import org.eclipse.jdt.core.dom.SimpleName;
56 import org.eclipse.jdt.core.dom.VariableDeclaration;
57 import org.eclipse.jdt.core.search.FieldDeclarationMatch;
58 import org.eclipse.jdt.core.search.MethodDeclarationMatch;
59 import org.eclipse.jdt.core.search.SearchMatch;
60
61 import org.eclipse.jdt.internal.corext.SourceRangeFactory;
62 import org.eclipse.jdt.internal.corext.dom.ASTNodes;
63 import org.eclipse.jdt.internal.corext.refactoring.RefactoringCoreMessages;
64 import org.eclipse.jdt.internal.corext.refactoring.SearchResultGroup;
65 import org.eclipse.jdt.internal.corext.refactoring.base.JavaStatusContext;
66 import org.eclipse.jdt.internal.corext.refactoring.base.JavaStringStatusContext;
67 import org.eclipse.jdt.internal.corext.refactoring.util.RefactoringASTParser;
68 import org.eclipse.jdt.internal.corext.refactoring.util.TextChangeManager;
69 import org.eclipse.jdt.internal.corext.util.Messages;
70 import org.eclipse.jdt.internal.corext.util.SearchUtils;
71
72 import org.eclipse.jdt.internal.ui.javaeditor.ASTProvider;
73 import org.eclipse.jdt.internal.ui.viewsupport.BasicElementLabels;
74
75 class RenameAnalyzeUtil {
76
77         private static class ProblemNodeFinder {
78
79                 private ProblemNodeFinder() {
80                         //static
81                 }
82
83                 public static SimpleName[] getProblemNodes(ASTNode methodNode, VariableDeclaration variableNode, TextEdit[] edits, TextChange change) {
84                         String key= variableNode.resolveBinding().getKey();
85                         NameNodeVisitor visitor= new NameNodeVisitor(edits, change, key);
86                         methodNode.accept(visitor);
87                         return visitor.getProblemNodes();
88                 }
89
90                 private static class NameNodeVisitor extends ASTVisitor {
91
92                         private Collection<IRegion> fRanges;
93                         private Collection<SimpleName> fProblemNodes;
94                         private String fKey;
95
96                         public NameNodeVisitor(TextEdit[] edits, TextChange change, String key) {
97                                 Assert.isNotNull(edits);
98                                 Assert.isNotNull(key);
99
100                                 fRanges= new HashSet<IRegion>(Arrays.asList(RefactoringAnalyzeUtil.getNewRanges(edits, change)));
101                                 fProblemNodes= new ArrayList<SimpleName>(0);
102                                 fKey= key;
103                         }
104
105                         public SimpleName[] getProblemNodes() {
106                                 return fProblemNodes.toArray(new SimpleName[fProblemNodes.size()]);
107                         }
108
109                         //----- visit methods
110
111                         @Override
112                         public boolean visit(SimpleName node) {
113                                 VariableDeclaration decl= getVariableDeclaration(node);
114                                 if (decl == null)
115                                         return super.visit(node);
116
117                                 IVariableBinding binding= decl.resolveBinding();
118                                 if (binding == null)
119                                         return super.visit(node);
120
121                                 boolean keysEqual= fKey.equals(binding.getKey());
122                                 boolean rangeInSet= fRanges.contains(new Region(node.getStartPosition(), node.getLength()));
123
124                                 if (keysEqual && !rangeInSet)
125                                         fProblemNodes.add(node);
126
127                                 if (!keysEqual && rangeInSet)
128                                         fProblemNodes.add(node);
129
130                                 /*
131                                  * if (!keyEquals && !rangeInSet)
132                                  *              ok, different local variable.
133                                  *
134                                  * if (keyEquals && rangeInSet)
135                                  *              ok, renamed local variable & has been renamed.
136                                  */
137
138                                 return super.visit(node);
139                         }
140                 }
141         }
142
143         static class LocalAnalyzePackage {
144                 public final TextEdit fDeclarationEdit;
145                 public final TextEdit[] fOccurenceEdits;
146
147                 public LocalAnalyzePackage(final TextEdit declarationEdit, final TextEdit[] occurenceEdits) {
148                         fDeclarationEdit = declarationEdit;
149                         fOccurenceEdits = occurenceEdits;
150                 }
151         }
152
153         private RenameAnalyzeUtil() {
154                 //no instance
155         }
156
157         static RefactoringStatus analyzeRenameChanges(TextChangeManager manager,  SearchResultGroup[] oldOccurrences, SearchResultGroup[] newOccurrences) {
158                 RefactoringStatus result= new RefactoringStatus();
159                 for (int i= 0; i < oldOccurrences.length; i++) {
160                         SearchResultGroup oldGroup= oldOccurrences[i];
161                         SearchMatch[] oldSearchResults= oldGroup.getSearchResults();
162                         ICompilationUnit cunit= oldGroup.getCompilationUnit();
163                         if (cunit == null)
164                                 continue;
165                         for (int j= 0; j < oldSearchResults.length; j++) {
166                                 SearchMatch oldSearchResult= oldSearchResults[j];
167                                 if (! RenameAnalyzeUtil.existsInNewOccurrences(oldSearchResult, newOccurrences, manager)){
168                                         addShadowsError(cunit, oldSearchResult, result);
169                                 }
170                         }
171                 }
172                 return result;
173         }
174
175         static ICompilationUnit findWorkingCopyForCu(ICompilationUnit[] newWorkingCopies, ICompilationUnit cu){
176                 ICompilationUnit original= cu == null ? null : cu.getPrimary();
177                 for (int i= 0; i < newWorkingCopies.length; i++) {
178                         if (newWorkingCopies[i].getPrimary().equals(original))
179                                 return newWorkingCopies[i];
180                 }
181                 return null;
182         }
183
184         static ICompilationUnit[] createNewWorkingCopies(ICompilationUnit[] compilationUnitsToModify, TextChangeManager manager, WorkingCopyOwner owner, SubProgressMonitor pm) throws CoreException {
185                 pm.beginTask("", compilationUnitsToModify.length); //$NON-NLS-1$
186                 ICompilationUnit[] newWorkingCopies= new ICompilationUnit[compilationUnitsToModify.length];
187                 for (int i= 0; i < compilationUnitsToModify.length; i++) {
188                         ICompilationUnit cu= compilationUnitsToModify[i];
189                         newWorkingCopies[i]= createNewWorkingCopy(cu, manager, owner, new SubProgressMonitor(pm, 1));
190                 }
191                 pm.done();
192                 return newWorkingCopies;
193         }
194
195         static ICompilationUnit createNewWorkingCopy(ICompilationUnit cu, TextChangeManager manager,
196                         WorkingCopyOwner owner, SubProgressMonitor pm) throws CoreException {
197                 ICompilationUnit newWc= cu.getWorkingCopy(owner, null);
198                 String previewContent= manager.get(cu).getPreviewContent(new NullProgressMonitor());
199                 newWc.getBuffer().setContents(previewContent);
200                 newWc.reconcile(ICompilationUnit.NO_AST, false, owner, pm);
201                 return newWc;
202         }
203
204         private static boolean existsInNewOccurrences(SearchMatch searchResult, SearchResultGroup[] newOccurrences, TextChangeManager manager) {
205                 SearchResultGroup newGroup= findOccurrenceGroup(searchResult.getResource(), newOccurrences);
206                 if (newGroup == null)
207                         return false;
208
209                 IRegion oldEditRange= getCorrespondingEditChangeRange(searchResult, manager);
210                 if (oldEditRange == null)
211                         return false;
212
213                 SearchMatch[] newSearchResults= newGroup.getSearchResults();
214                 int oldRangeOffset = oldEditRange.getOffset();
215                 for (int i= 0; i < newSearchResults.length; i++) {
216                         if (newSearchResults[i].getOffset() == oldRangeOffset)
217                                 return true;
218                 }
219                 return false;
220         }
221
222         private static IRegion getCorrespondingEditChangeRange(SearchMatch searchResult, TextChangeManager manager) {
223                 TextChange change= getTextChange(searchResult, manager);
224                 if (change == null)
225                         return null;
226
227                 IRegion oldMatchRange= createTextRange(searchResult);
228                 TextEditChangeGroup[] editChanges= change.getTextEditChangeGroups();
229                 for (int i= 0; i < editChanges.length; i++) {
230                         if (oldMatchRange.equals(editChanges[i].getRegion()))
231                                 return TextEdit.getCoverage(change.getPreviewEdits(editChanges[i].getTextEdits()));
232                 }
233                 return null;
234         }
235
236         private static TextChange getTextChange(SearchMatch searchResult, TextChangeManager manager) {
237                 ICompilationUnit cu= SearchUtils.getCompilationUnit(searchResult);
238                 if (cu == null)
239                         return null;
240                 return manager.get(cu);
241         }
242
243         private static IRegion createTextRange(SearchMatch searchResult) {
244                 return new Region(searchResult.getOffset(), searchResult.getLength());
245         }
246
247         private static SearchResultGroup findOccurrenceGroup(IResource resource, SearchResultGroup[] newOccurrences) {
248                 for (int i= 0; i < newOccurrences.length; i++) {
249                         if (newOccurrences[i].getResource().equals(resource))
250                                 return newOccurrences[i];
251                 }
252                 return null;
253         }
254
255 //--- find missing changes in BOTH directions
256
257         //TODO: Currently filters out declarations (MethodDeclarationMatch, FieldDeclarationMatch).
258         //Long term solution: only pass reference search results in.
259         static RefactoringStatus analyzeRenameChanges2(TextChangeManager manager,
260                         SearchResultGroup[] oldReferences, SearchResultGroup[] newReferences, String newElementName) {
261                 RefactoringStatus result= new RefactoringStatus();
262
263                 HashMap<ICompilationUnit, SearchMatch[]> cuToNewResults= new HashMap<ICompilationUnit, SearchMatch[]>(newReferences.length);
264                 for (int i1= 0; i1 < newReferences.length; i1++) {
265                         ICompilationUnit cu= newReferences[i1].getCompilationUnit();
266                         if (cu != null)
267                                 cuToNewResults.put(cu.getPrimary(), newReferences[i1].getSearchResults());
268                 }
269
270                 for (int i= 0; i < oldReferences.length; i++) {
271                         SearchResultGroup oldGroup= oldReferences[i];
272                         SearchMatch[] oldMatches= oldGroup.getSearchResults();
273                         ICompilationUnit cu= oldGroup.getCompilationUnit();
274                         if (cu == null)
275                                 continue;
276
277                         SearchMatch[] newSearchMatches= cuToNewResults.remove(cu);
278                         if (newSearchMatches == null) {
279                                 for (int j = 0; j < oldMatches.length; j++) {
280                                         SearchMatch oldMatch = oldMatches[j];
281                                         addShadowsError(cu, oldMatch, result);
282                                 }
283                         } else {
284                                 analyzeChanges(cu, manager.get(cu), oldMatches, newSearchMatches, newElementName, result);
285                         }
286                 }
287
288                 for (Iterator<Entry<ICompilationUnit, SearchMatch[]>> iter= cuToNewResults.entrySet().iterator(); iter.hasNext();) {
289                         Entry<ICompilationUnit, SearchMatch[]> entry= iter.next();
290                         ICompilationUnit cu= entry.getKey();
291                         SearchMatch[] newSearchMatches= entry.getValue();
292                         for (int i= 0; i < newSearchMatches.length; i++) {
293                                 SearchMatch newMatch= newSearchMatches[i];
294                                 addReferenceShadowedError(cu, newMatch, newElementName, result);
295                         }
296                 }
297                 return result;
298         }
299
300         private static void analyzeChanges(ICompilationUnit cu, TextChange change,
301                         SearchMatch[] oldMatches, SearchMatch[] newMatches, String newElementName, RefactoringStatus result) {
302                 Map<Integer, SearchMatch> updatedOldOffsets= getUpdatedChangeOffsets(change, oldMatches);
303                 for (int i= 0; i < newMatches.length; i++) {
304                         SearchMatch newMatch= newMatches[i];
305                         Integer offsetInNew= new Integer(newMatch.getOffset());
306                         SearchMatch oldMatch= updatedOldOffsets.remove(offsetInNew);
307                         if (oldMatch == null) {
308                                 addReferenceShadowedError(cu, newMatch, newElementName, result);
309                         }
310                 }
311                 for (Iterator<SearchMatch> iter= updatedOldOffsets.values().iterator(); iter.hasNext();) {
312                         // remaining old matches are not found any more -> they have been shadowed
313                         SearchMatch oldMatch= iter.next();
314                         addShadowsError(cu, oldMatch, result);
315                 }
316         }
317
318         /**
319          *
320          * @param change
321          * @param oldMatches
322          * @return Map &lt;Integer updatedOffset, SearchMatch oldMatch&gt;
323          */
324         private static Map<Integer, SearchMatch> getUpdatedChangeOffsets(TextChange change, SearchMatch[] oldMatches) {
325                 Map<Integer, SearchMatch> updatedOffsets= new HashMap<Integer, SearchMatch>();
326                 Map<Integer, Integer> oldToUpdatedOffsets= getEditChangeOffsetUpdates(change);
327                 for (int i= 0; i < oldMatches.length; i++) {
328                         SearchMatch oldMatch= oldMatches[i];
329                         Integer updatedOffset= oldToUpdatedOffsets.get(new Integer(oldMatch.getOffset()));
330                         if (updatedOffset == null)
331                                 updatedOffset= new Integer(-1); //match not updated
332                         updatedOffsets.put(updatedOffset, oldMatch);
333                 }
334                 return updatedOffsets;
335         }
336
337         /**
338          *
339          * @param change
340          * @return Map &lt;Integer oldOffset, Integer updatedOffset&gt;
341          */
342         private static Map<Integer, Integer> getEditChangeOffsetUpdates(TextChange change) {
343                 TextEditChangeGroup[] editChanges= change.getTextEditChangeGroups();
344                 Map<Integer, Integer> offsetUpdates= new HashMap<Integer, Integer>(editChanges.length);
345                 for (int i= 0; i < editChanges.length; i++) {
346                         TextEditChangeGroup editChange= editChanges[i];
347                         IRegion oldRegion= editChange.getRegion();
348                         if (oldRegion == null)
349                                 continue;
350                         IRegion updatedRegion= TextEdit.getCoverage(change.getPreviewEdits(editChange.getTextEdits()));
351                         if (updatedRegion == null)
352                                 continue;
353
354                         offsetUpdates.put(new Integer(oldRegion.getOffset()), new Integer(updatedRegion.getOffset()));
355                 }
356                 return offsetUpdates;
357         }
358
359         private static void addReferenceShadowedError(ICompilationUnit cu, SearchMatch newMatch, String newElementName, RefactoringStatus result) {
360                 //Found a new match with no corresponding old match.
361                 //-> The new match is a reference which was pointing to another element,
362                 //but that other element has been shadowed
363
364                 //TODO: should not have to filter declarations:
365                 if (newMatch instanceof MethodDeclarationMatch || newMatch instanceof FieldDeclarationMatch)
366                         return;
367                 ISourceRange range= getOldSourceRange(newMatch);
368                 RefactoringStatusContext context= JavaStatusContext.create(cu, range);
369                 String message= Messages.format(
370                                 RefactoringCoreMessages.RenameAnalyzeUtil_reference_shadowed,
371                                 new String[] {BasicElementLabels.getFileName(cu), BasicElementLabels.getJavaElementName(newElementName)});
372                 result.addError(message, context);
373         }
374
375         private static ISourceRange getOldSourceRange(SearchMatch newMatch) {
376                 // cannot transfom offset in preview to offset in original -> just show enclosing method
377                 IJavaElement newMatchElement= (IJavaElement) newMatch.getElement();
378                 IJavaElement primaryElement= newMatchElement.getPrimaryElement();
379                 ISourceRange range= null;
380                 if (primaryElement.exists() && primaryElement instanceof ISourceReference) {
381                         try {
382                                 range= ((ISourceReference) primaryElement).getSourceRange();
383                         } catch (JavaModelException e) {
384                                 // can live without source range
385                         }
386                 }
387                 return range;
388         }
389
390         private static void addShadowsError(ICompilationUnit cu, SearchMatch oldMatch, RefactoringStatus result) {
391                 // Old match not found in new matches -> reference has been shadowed
392
393                 //TODO: should not have to filter declarations:
394                 if (oldMatch instanceof MethodDeclarationMatch || oldMatch instanceof FieldDeclarationMatch)
395                         return;
396                 ISourceRange range= new SourceRange(oldMatch.getOffset(), oldMatch.getLength());
397                 RefactoringStatusContext context= JavaStatusContext.create(cu, range);
398                 String message= Messages.format(RefactoringCoreMessages.RenameAnalyzeUtil_shadows, BasicElementLabels.getFileName(cu));
399                 result.addError(message, context);
400         }
401
402         /**
403          * This method analyzes a set of local variable renames inside one cu. It checks whether
404          * any new compile errors have been introduced by the rename(s) and whether the correct
405          * node(s) has/have been renamed.
406          *
407          * @param analyzePackages the LocalAnalyzePackages containing the information about the local renames
408          * @param cuChange the TextChange containing all local variable changes to be applied.
409          * @param oldCUNode the fully (incl. bindings) resolved AST node of the original compilation unit
410          * @param recovery whether statements and bindings recovery should be performed when parsing the changed CU
411          * @return a RefactoringStatus containing errors if compile errors or wrongly renamed nodes are found
412          * @throws CoreException thrown if there was an error greating the preview content of the change
413          */
414         public static RefactoringStatus analyzeLocalRenames(LocalAnalyzePackage[] analyzePackages, TextChange cuChange, CompilationUnit oldCUNode, boolean recovery) throws CoreException {
415
416                 RefactoringStatus result= new RefactoringStatus();
417                 ICompilationUnit compilationUnit= (ICompilationUnit) oldCUNode.getJavaElement();
418
419                 String newCuSource= cuChange.getPreviewContent(new NullProgressMonitor());
420                 CompilationUnit newCUNode= new RefactoringASTParser(ASTProvider.SHARED_AST_LEVEL).parse(newCuSource, compilationUnit, true, recovery, null);
421
422                 result.merge(analyzeCompileErrors(newCuSource, newCUNode, oldCUNode));
423                 if (result.hasError())
424                         return result;
425
426                 for (int i= 0; i < analyzePackages.length; i++) {
427                         ASTNode enclosing= getEnclosingBlockOrMethod(analyzePackages[i].fDeclarationEdit, cuChange, newCUNode);
428
429                         // get new declaration
430                         IRegion newRegion= RefactoringAnalyzeUtil.getNewTextRange(analyzePackages[i].fDeclarationEdit, cuChange);
431                         ASTNode newDeclaration= NodeFinder.perform(newCUNode, newRegion.getOffset(), newRegion.getLength());
432                         Assert.isTrue(newDeclaration instanceof Name);
433
434                         VariableDeclaration declaration= getVariableDeclaration((Name) newDeclaration);
435                         Assert.isNotNull(declaration);
436
437                         SimpleName[] problemNodes= ProblemNodeFinder.getProblemNodes(enclosing, declaration, analyzePackages[i].fOccurenceEdits, cuChange);
438                         result.merge(RefactoringAnalyzeUtil.reportProblemNodes(newCuSource, problemNodes));
439                 }
440                 return result;
441         }
442
443         private static VariableDeclaration getVariableDeclaration(Name node) {
444                 IBinding binding= node.resolveBinding();
445                 if (binding == null && node.getParent() instanceof VariableDeclaration)
446                         return (VariableDeclaration) node.getParent();
447
448                 if (binding != null && binding.getKind() == IBinding.VARIABLE) {
449                         CompilationUnit cu= (CompilationUnit) ASTNodes.getParent(node, CompilationUnit.class);
450                         return ASTNodes.findVariableDeclaration( ((IVariableBinding) binding), cu);
451                 }
452                 return null;
453         }
454
455         private static ASTNode getEnclosingBlockOrMethod(TextEdit declarationEdit, TextChange change, CompilationUnit newCUNode) {
456                 ASTNode enclosing= RefactoringAnalyzeUtil.getBlock(declarationEdit, change, newCUNode);
457                 if (enclosing == null)
458                         enclosing= RefactoringAnalyzeUtil.getMethodDeclaration(declarationEdit, change, newCUNode);
459                 return enclosing;
460         }
461
462         private static RefactoringStatus analyzeCompileErrors(String newCuSource, CompilationUnit newCUNode, CompilationUnit oldCUNode) {
463                 RefactoringStatus result= new RefactoringStatus();
464                 IProblem[] newProblems= RefactoringAnalyzeUtil.getIntroducedCompileProblems(newCUNode, oldCUNode);
465                 for (int i= 0; i < newProblems.length; i++) {
466                         IProblem problem= newProblems[i];
467                         if (problem.isError())
468                                 result.addEntry(new RefactoringStatusEntry((problem.isError() ? RefactoringStatus.ERROR : RefactoringStatus.WARNING), problem.getMessage(), new JavaStringStatusContext(newCuSource,
469                                                 SourceRangeFactory.create(problem))));
470                 }
471                 return result;
472         }
473 }