]> git.uio.no Git - ifi-stolz-refaktor.git/blob - case-study/jdt-after/ui/org/eclipse/jdt/internal/ui/fix/NullRewriteOperations.java
Case Study: adding data and statistics
[ifi-stolz-refaktor.git] / case-study / jdt-after / ui / org / eclipse / jdt / internal / ui / fix / NullRewriteOperations.java
1 /*******************************************************************************
2  * Copyright (c) 2011, 2012 GK Software AG 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  *     Stephan Herrmann <stephan@cs.tu-berlin.de> - [quick fix] Add quick fixes for null annotations - https://bugs.eclipse.org/337977
10  *     IBM Corporation - bug fixes
11  *******************************************************************************/
12 package org.eclipse.jdt.internal.ui.fix;
13
14 import java.util.List;
15 import java.util.Set;
16
17 import org.eclipse.core.runtime.CoreException;
18
19 import org.eclipse.text.edits.TextEditGroup;
20
21 import org.eclipse.jdt.core.ICompilationUnit;
22 import org.eclipse.jdt.core.JavaModelException;
23 import org.eclipse.jdt.core.compiler.IProblem;
24 import org.eclipse.jdt.core.dom.AST;
25 import org.eclipse.jdt.core.dom.ASTNode;
26 import org.eclipse.jdt.core.dom.Annotation;
27 import org.eclipse.jdt.core.dom.BodyDeclaration;
28 import org.eclipse.jdt.core.dom.CompilationUnit;
29 import org.eclipse.jdt.core.dom.IBinding;
30 import org.eclipse.jdt.core.dom.IExtendedModifier;
31 import org.eclipse.jdt.core.dom.IMethodBinding;
32 import org.eclipse.jdt.core.dom.ITypeBinding;
33 import org.eclipse.jdt.core.dom.IVariableBinding;
34 import org.eclipse.jdt.core.dom.MarkerAnnotation;
35 import org.eclipse.jdt.core.dom.MethodDeclaration;
36 import org.eclipse.jdt.core.dom.MethodInvocation;
37 import org.eclipse.jdt.core.dom.Name;
38 import org.eclipse.jdt.core.dom.SimpleName;
39 import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
40 import org.eclipse.jdt.core.dom.VariableDeclaration;
41 import org.eclipse.jdt.core.dom.rewrite.ImportRewrite;
42 import org.eclipse.jdt.core.dom.rewrite.ListRewrite;
43
44 import org.eclipse.jdt.internal.corext.dom.ASTNodes;
45 import org.eclipse.jdt.internal.corext.dom.Bindings;
46 import org.eclipse.jdt.internal.corext.fix.CompilationUnitRewriteOperationsFix.CompilationUnitRewriteOperation;
47 import org.eclipse.jdt.internal.corext.fix.LinkedProposalModel;
48 import org.eclipse.jdt.internal.corext.refactoring.structure.CompilationUnitRewrite;
49 import org.eclipse.jdt.internal.corext.util.JavaModelUtil;
50 import org.eclipse.jdt.internal.corext.util.Messages;
51
52 import org.eclipse.jdt.ui.text.java.IProblemLocation;
53
54 import org.eclipse.jdt.internal.ui.text.correction.ASTResolving;
55 import org.eclipse.jdt.internal.ui.viewsupport.BasicElementLabels;
56
57 public class NullRewriteOperations {
58
59         static abstract class SignatureAnnotationRewriteOperation extends CompilationUnitRewriteOperation {
60                 String fAnnotationToAdd;
61                 String fAnnotationToRemove;
62                 boolean fAllowRemove;
63                 CompilationUnit fUnit;
64                 protected String fKey;
65                 protected String fMessage;
66
67                 /* A globally unique key that identifies the position being annotated (for avoiding double annotations). */
68                 public String getKey() {
69                         return this.fKey;
70                 }
71
72                 public CompilationUnit getCompilationUnit() {
73                         return fUnit;
74                 }
75
76                 boolean checkExisting(List<IExtendedModifier> existingModifiers, ListRewrite listRewrite, TextEditGroup editGroup) {
77                         for (Object mod : existingModifiers) {
78                                 if (mod instanceof MarkerAnnotation) {
79                                         MarkerAnnotation annotation= (MarkerAnnotation) mod;
80                                         String existingName= annotation.getTypeName().getFullyQualifiedName();
81                                         int lastDot= fAnnotationToRemove.lastIndexOf('.');
82                                         if (existingName.equals(fAnnotationToRemove) || (lastDot != -1 && fAnnotationToRemove.substring(lastDot + 1).equals(existingName))) {
83                                                 if (!fAllowRemove)
84                                                         return false; // veto this change
85                                                 listRewrite.remove(annotation, editGroup);
86                                                 return true;
87                                         }
88                                         // paranoia: check if by accident the annotation is already present (shouldn't happen):
89                                         lastDot= fAnnotationToAdd.lastIndexOf('.');
90                                         if (existingName.equals(fAnnotationToAdd) || (lastDot != -1 && fAnnotationToAdd.substring(lastDot + 1).equals(existingName))) {
91                                                 return false; // already present
92                                         }
93                                 }
94                         }
95                         return true;
96                 }
97
98                 public String getMessage() {
99                         return fMessage;
100                 }
101         }
102
103         /**
104          * Rewrite operation that inserts an annotation into a method signature.
105          * 
106          * Crafted after the lead of Java50Fix.AnnotationRewriteOperation
107          */
108         static class ReturnAnnotationRewriteOperation extends SignatureAnnotationRewriteOperation {
109
110                 private final BodyDeclaration fBodyDeclaration;
111
112                 ReturnAnnotationRewriteOperation(CompilationUnit unit, MethodDeclaration method, String annotationToAdd, String annotationToRemove, boolean allowRemove, String message) {
113                         fUnit= unit;
114                         fKey= method.resolveBinding().getKey() + "<return>"; //$NON-NLS-1$
115                         fBodyDeclaration= method;
116                         fAnnotationToAdd= annotationToAdd;
117                         fAnnotationToRemove= annotationToRemove;
118                         fAllowRemove= allowRemove;
119                         fMessage= message;
120                 }
121
122                 @Override
123                 public void rewriteAST(CompilationUnitRewrite cuRewrite, LinkedProposalModel model) throws CoreException {
124                         AST ast= cuRewrite.getRoot().getAST();
125                         ListRewrite listRewrite= cuRewrite.getASTRewrite().getListRewrite(fBodyDeclaration, fBodyDeclaration.getModifiersProperty());
126                         TextEditGroup group= createTextEditGroup(fMessage, cuRewrite);
127                         if (!checkExisting(fBodyDeclaration.modifiers(), listRewrite, group))
128                                 return;
129                         Annotation newAnnotation= ast.newMarkerAnnotation();
130                         ImportRewrite importRewrite= cuRewrite.getImportRewrite();
131                         String resolvableName= importRewrite.addImport(fAnnotationToAdd);
132                         newAnnotation.setTypeName(ast.newName(resolvableName));
133                         listRewrite.insertLast(newAnnotation, group); // null annotation is last modifier, directly preceding the return type
134                 }
135         }
136
137         static class ParameterAnnotationRewriteOperation extends SignatureAnnotationRewriteOperation {
138
139                 private SingleVariableDeclaration fArgument;
140
141                 ParameterAnnotationRewriteOperation(CompilationUnit unit, MethodDeclaration method, String annotationToAdd, String annotationToRemove, String paramName, boolean allowRemove, String message) {
142                         fUnit= unit;
143                         fKey= method.resolveBinding().getKey();
144                         fAnnotationToAdd= annotationToAdd;
145                         fAnnotationToRemove= annotationToRemove;
146                         fAllowRemove= allowRemove;
147                         fMessage= message;
148                         for (Object param : method.parameters()) {
149                                 SingleVariableDeclaration argument= (SingleVariableDeclaration) param;
150                                 if (argument.getName().getIdentifier().equals(paramName)) {
151                                         fArgument= argument;
152                                         fKey+= argument.getName().getIdentifier();
153                                         return;
154                                 }
155                         }
156                         // shouldn't happen, we've checked that paramName indeed denotes a parameter.
157                         throw new RuntimeException("Argument " + paramName + " not found in method " + method.getName().getIdentifier()); //$NON-NLS-1$ //$NON-NLS-2$
158                 }
159
160                 ParameterAnnotationRewriteOperation(CompilationUnit unit, MethodDeclaration method, String annotationToAdd, String annotationToRemove, int paramIdx, boolean allowRemove, String message) {
161                         fUnit= unit;
162                         fKey= method.resolveBinding().getKey();
163                         fAnnotationToAdd= annotationToAdd;
164                         fAnnotationToRemove= annotationToRemove;
165                         fAllowRemove= allowRemove;
166                         fArgument= (SingleVariableDeclaration) method.parameters().get(paramIdx);
167                         fKey+= fArgument.getName().getIdentifier();
168                         fMessage= message;
169                 }
170
171                 @Override
172                 public void rewriteAST(CompilationUnitRewrite cuRewrite, LinkedProposalModel linkedModel) throws CoreException {
173                         AST ast= cuRewrite.getRoot().getAST();
174                         ListRewrite listRewrite= cuRewrite.getASTRewrite().getListRewrite(fArgument, SingleVariableDeclaration.MODIFIERS2_PROPERTY);
175                         TextEditGroup group= createTextEditGroup(fMessage, cuRewrite);
176                         if (!checkExisting(fArgument.modifiers(), listRewrite, group))
177                                 return;
178                         Annotation newAnnotation= ast.newMarkerAnnotation();
179                         ImportRewrite importRewrite= cuRewrite.getImportRewrite();
180                         String resolvableName= importRewrite.addImport(fAnnotationToAdd);
181                         newAnnotation.setTypeName(ast.newName(resolvableName));
182                         listRewrite.insertLast(newAnnotation, group); // null annotation is last modifier, directly preceding the type
183                 }
184         }
185
186         // Entry for QuickFixes:
187         public static SignatureAnnotationRewriteOperation createAddAnnotationOperation(CompilationUnit compilationUnit, IProblemLocation problem, String annotationToAdd, String annotationToRemove,
188                         Set<String> handledPositions, boolean thisUnitOnly, boolean allowRemove, boolean modifyOverridden) {
189                 SignatureAnnotationRewriteOperation result= modifyOverridden ? createAddAnnotationToOverriddenOperation(compilationUnit, problem, annotationToAdd, annotationToRemove, handledPositions,
190                                 thisUnitOnly, allowRemove) : createAddAnnotationOperation(compilationUnit, problem, annotationToAdd, annotationToRemove, handledPositions, thisUnitOnly, allowRemove);
191                 if (handledPositions != null && result != null) {
192                         if (handledPositions.contains(result.getKey()))
193                                 return null;
194                         handledPositions.add(result.getKey());
195                 }
196                 return result;
197         }
198
199         private static SignatureAnnotationRewriteOperation createAddAnnotationOperation(CompilationUnit compilationUnit, IProblemLocation problem, String annotationToAdd, String annotationToRemove,
200                         Set<String> handledPositions, boolean thisUnitOnly, boolean allowRemove) {
201                 ICompilationUnit cu= (ICompilationUnit) compilationUnit.getJavaElement();
202                 if (!JavaModelUtil.is50OrHigher(cu.getJavaProject()))
203                         return null;
204
205                 ASTNode selectedNode= problem.getCoveringNode(compilationUnit);
206                 if (selectedNode == null)
207                         return null;
208                 ASTNode declaringNode= getDeclaringNode(selectedNode);
209
210                 switch (problem.getProblemId()) {
211                         case IProblem.IllegalDefinitionToNonNullParameter:
212 //                      case IllegalRedefinitionToNonNullParameter:
213                                 // these affect another method
214                                 break;
215                         case IProblem.IllegalReturnNullityRedefinition:
216                                 if (declaringNode == null)
217                                         declaringNode= selectedNode;
218                                 break; // do propose changes even if we already have an annotation
219                         default:
220                                 // if this method has annotations, don't change'em
221                                 if (NullQuickFixes.hasExplicitNullAnnotation(cu, problem.getOffset()))
222                                         return null;
223                 }
224
225                 String annotationNameLabel= annotationToAdd;
226                 int lastDot= annotationToAdd.lastIndexOf('.');
227                 if (lastDot != -1)
228                         annotationNameLabel= annotationToAdd.substring(lastDot + 1);
229                 annotationNameLabel= BasicElementLabels.getJavaElementName(annotationNameLabel);
230
231                 if (selectedNode.getParent() instanceof MethodInvocation) {
232                         // DefiniteNullToNonNullParameter || PotentialNullToNonNullParameter
233                         MethodInvocation methodInvocation= (MethodInvocation) selectedNode.getParent();
234                         int paramIdx= methodInvocation.arguments().indexOf(selectedNode);
235                         IMethodBinding methodBinding= methodInvocation.resolveMethodBinding();
236                         compilationUnit= findCUForMethod(compilationUnit, cu, methodBinding);
237                         if (compilationUnit == null)
238                                 return null;
239                         if (thisUnitOnly && !compilationUnit.getJavaElement().equals(cu))
240                                 return null;
241                         ASTNode methodDecl= compilationUnit.findDeclaringNode(methodBinding.getKey());
242                         if (methodDecl == null)
243                                 return null;
244                         String message= Messages.format(NullFixMessages.QuickFixes_change_method_parameter_nullness, annotationNameLabel);
245                         return new ParameterAnnotationRewriteOperation(compilationUnit, (MethodDeclaration) methodDecl, annotationToAdd, annotationToRemove, paramIdx, allowRemove, message);
246                 } else if (declaringNode instanceof MethodDeclaration) {
247                         // complaint is in signature of this method
248                         MethodDeclaration declaration= (MethodDeclaration) declaringNode;
249                         switch (problem.getProblemId()) {
250                                 case IProblem.ParameterLackingNonNullAnnotation:
251                                 case IProblem.ParameterLackingNullableAnnotation:
252                                 case IProblem.IllegalDefinitionToNonNullParameter:
253                                 case IProblem.IllegalRedefinitionToNonNullParameter:
254                                 case IProblem.NonNullLocalVariableComparisonYieldsFalse:
255                                 case IProblem.RedundantNullCheckOnNonNullLocalVariable:
256                                         // statements suggest changing parameters:
257                                         if (declaration.getNodeType() == ASTNode.METHOD_DECLARATION) {
258                                                 String paramName= findAffectedParameterName(selectedNode);
259                                                 if (paramName != null) {
260                                                         String message= Messages.format(NullFixMessages.QuickFixes_change_method_parameter_nullness, annotationNameLabel);
261                                                         return new ParameterAnnotationRewriteOperation(compilationUnit, declaration, annotationToAdd, annotationToRemove, paramName, allowRemove, message);
262                                                 }
263                                         }
264                                         break;
265                                 case IProblem.RequiredNonNullButProvidedNull:
266                                 case IProblem.RequiredNonNullButProvidedPotentialNull:
267                                 case IProblem.RequiredNonNullButProvidedUnknown:
268                                         if (NullQuickFixes.isComplainingAboutArgument(selectedNode)) {
269                                                 //TODO: duplication
270                                                 // statements suggest changing parameters:
271                                                 if (declaration.getNodeType() == ASTNode.METHOD_DECLARATION) {
272                                                         String paramName= findAffectedParameterName(selectedNode);
273                                                         if (paramName != null) {
274                                                                 String message= Messages.format(NullFixMessages.QuickFixes_change_method_parameter_nullness, annotationNameLabel);
275                                                                 return new ParameterAnnotationRewriteOperation(compilationUnit, declaration, annotationToAdd, annotationToRemove, paramName, allowRemove, message);
276                                                         }
277                                                 }
278                                                 break;
279                                         }
280                                         //$FALL-THROUGH$
281                                 case IProblem.IllegalReturnNullityRedefinition:
282                                         String message= Messages.format(NullFixMessages.QuickFixes_change_method_return_nullness, new String[] { declaration.getName().getIdentifier(), annotationNameLabel });
283                                         return new ReturnAnnotationRewriteOperation(compilationUnit, declaration, annotationToAdd, annotationToRemove, allowRemove, message);
284                         }
285                 }
286                 return null;
287         }
288
289         private static SignatureAnnotationRewriteOperation createAddAnnotationToOverriddenOperation(CompilationUnit compilationUnit, IProblemLocation problem, String annotationToAdd,
290                         String annotationToRemove, Set<String> handledPositions, boolean thisUnitOnly, boolean allowRemove) {
291                 ICompilationUnit cu= (ICompilationUnit) compilationUnit.getJavaElement();
292                 if (!JavaModelUtil.is50OrHigher(cu.getJavaProject()))
293                         return null;
294
295                 ASTNode selectedNode= problem.getCoveringNode(compilationUnit);
296                 if (selectedNode == null)
297                         return null;
298
299                 ASTNode declaringNode= getDeclaringNode(selectedNode);
300                 switch (problem.getProblemId()) {
301                         case IProblem.IllegalDefinitionToNonNullParameter:
302                         case IProblem.IllegalRedefinitionToNonNullParameter:
303                                 break;
304                         case IProblem.IllegalReturnNullityRedefinition:
305                                 if (declaringNode == null)
306                                         declaringNode= selectedNode;
307                                 break;
308                         default:
309                                 return null;
310                 }
311
312                 String annotationNameLabel= annotationToAdd;
313                 int lastDot= annotationToAdd.lastIndexOf('.');
314                 if (lastDot != -1)
315                         annotationNameLabel= annotationToAdd.substring(lastDot + 1);
316                 annotationNameLabel= BasicElementLabels.getJavaElementName(annotationNameLabel);
317
318                 if (declaringNode instanceof MethodDeclaration) {
319                         // complaint is in signature of this method
320                         MethodDeclaration declaration= (MethodDeclaration) declaringNode;
321                         switch (problem.getProblemId()) {
322                                 case IProblem.IllegalDefinitionToNonNullParameter:
323                                 case IProblem.IllegalRedefinitionToNonNullParameter:
324                                         return createChangeOverriddenParameterOperation(compilationUnit, cu, declaration, selectedNode, allowRemove, annotationToAdd, annotationToRemove, annotationNameLabel);
325                                 case IProblem.IllegalReturnNullityRedefinition:
326                                         if (hasNullAnnotation(declaration)) { // don't adjust super if local has no explicit annotation (?)
327                                                 return createChangeOverriddenReturnOperation(compilationUnit, cu, declaration, allowRemove, annotationToAdd, annotationToRemove, annotationNameLabel);
328                                         }
329                         }
330                 }
331                 return null;
332         }
333
334         private static SignatureAnnotationRewriteOperation createChangeOverriddenParameterOperation(CompilationUnit compilationUnit, ICompilationUnit cu, MethodDeclaration declaration,
335                         ASTNode selectedNode, boolean allowRemove, String annotationToAdd, String annotationToRemove, String annotationNameLabel) {
336                 IMethodBinding methodDeclBinding= declaration.resolveBinding();
337                 if (methodDeclBinding == null)
338                         return null;
339
340                 IMethodBinding overridden= Bindings.findOverriddenMethod(methodDeclBinding, false);
341                 if (overridden == null)
342                         return null;
343                 compilationUnit= findCUForMethod(compilationUnit, cu, overridden);
344                 if (compilationUnit == null)
345                         return null;
346                 ASTNode methodDecl= compilationUnit.findDeclaringNode(overridden.getKey());
347                 if (methodDecl == null)
348                         return null;
349                 declaration= (MethodDeclaration) methodDecl;
350                 String message= Messages.format(NullFixMessages.QuickFixes_change_overridden_parameter_nullness, new String[] { overridden.getName(), annotationNameLabel });
351                 String paramName= findAffectedParameterName(selectedNode);
352                 return new ParameterAnnotationRewriteOperation(compilationUnit, declaration, annotationToAdd, annotationToRemove, paramName, allowRemove, message);
353         }
354
355         private static String findAffectedParameterName(ASTNode selectedNode) {
356                 VariableDeclaration argDecl= (selectedNode instanceof VariableDeclaration) ? (VariableDeclaration) selectedNode : (VariableDeclaration) ASTNodes.getParent(selectedNode,
357                                 VariableDeclaration.class);
358                 if (argDecl != null)
359                         return argDecl.getName().getIdentifier();
360                 if (selectedNode.getNodeType() == ASTNode.SIMPLE_NAME) {
361                         IBinding binding= ((SimpleName) selectedNode).resolveBinding();
362                         if (binding.getKind() == IBinding.VARIABLE && ((IVariableBinding) binding).isParameter())
363                                 return ((SimpleName) selectedNode).getIdentifier();
364                 }
365                 return null;
366         }
367
368         private static boolean hasNullAnnotation(MethodDeclaration decl) {
369                 List<IExtendedModifier> modifiers= decl.modifiers();
370                 String nonnull= NullQuickFixes.getNonNullAnnotationName(decl.resolveBinding().getJavaElement(), false);
371                 String nullable= NullQuickFixes.getNullableAnnotationName(decl.resolveBinding().getJavaElement(), false);
372                 for (Object mod : modifiers) {
373                         if (mod instanceof Annotation) {
374                                 Name annotationName= ((Annotation) mod).getTypeName();
375                                 String fullyQualifiedName= annotationName.getFullyQualifiedName();
376                                 if (annotationName.isSimpleName() ? nonnull.endsWith(fullyQualifiedName) : fullyQualifiedName.equals(nonnull))
377                                         return true;
378                                 if (annotationName.isSimpleName() ? nullable.endsWith(fullyQualifiedName) : fullyQualifiedName.equals(nullable))
379                                         return true;
380                         }
381                 }
382                 return false;
383         }
384
385         private static SignatureAnnotationRewriteOperation createChangeOverriddenReturnOperation(CompilationUnit compilationUnit, ICompilationUnit cu, MethodDeclaration declaration, boolean allowRemove,
386                         String annotationToAdd, String annotationToRemove, String annotationNameLabel) {
387                 IMethodBinding methodDeclBinding= declaration.resolveBinding();
388                 if (methodDeclBinding == null)
389                         return null;
390
391                 IMethodBinding overridden= Bindings.findOverriddenMethod(methodDeclBinding, false);
392                 if (overridden == null)
393                         return null;
394                 compilationUnit= findCUForMethod(compilationUnit, cu, overridden);
395                 if (compilationUnit == null)
396                         return null;
397                 ASTNode methodDecl= compilationUnit.findDeclaringNode(overridden.getKey());
398                 if (methodDecl == null)
399                         return null;
400                 declaration= (MethodDeclaration) methodDecl;
401 // TODO(SH): decide whether we want to propose overwriting existing annotations in super
402 //              if (hasNullAnnotation(declaration)) // if overridden has explicit declaration don't propose to change it
403 //                      return null;
404                 String message= Messages.format(NullFixMessages.QuickFixes_change_overridden_return_nullness, new String[] { overridden.getName(), annotationNameLabel });
405                 return new ReturnAnnotationRewriteOperation(compilationUnit, declaration, annotationToAdd, annotationToRemove, allowRemove, message);
406         }
407
408         private static CompilationUnit findCUForMethod(CompilationUnit compilationUnit, ICompilationUnit cu, IMethodBinding methodBinding) {
409                 ASTNode methodDecl= compilationUnit.findDeclaringNode(methodBinding.getMethodDeclaration());
410                 if (methodDecl == null) {
411                         // is methodDecl defined in another CU?
412                         ITypeBinding declaringTypeDecl= methodBinding.getDeclaringClass().getTypeDeclaration();
413                         if (declaringTypeDecl.isFromSource()) {
414                                 ICompilationUnit targetCU= null;
415                                 try {
416                                         targetCU= ASTResolving.findCompilationUnitForBinding(cu, compilationUnit, declaringTypeDecl);
417                                 } catch (JavaModelException e) { /* can't do better */
418                                 }
419                                 if (targetCU != null) {
420                                         return ASTResolving.createQuickFixAST(targetCU, null);
421                                 }
422                         }
423                         return null;
424                 }
425                 return compilationUnit;
426         }
427
428         /* The relevant declaring node of a return statement is the enclosing method. */
429         private static ASTNode getDeclaringNode(ASTNode selectedNode) {
430                 return ASTNodes.getParent(selectedNode, ASTNode.METHOD_DECLARATION);
431         }
432 }