]> git.uio.no Git - ifi-stolz-refaktor.git/blob - case-study/jdt-after/ui/org/eclipse/jdt/internal/ui/fix/NullQuickFixes.java
Case Study: adding data and statistics
[ifi-stolz-refaktor.git] / case-study / jdt-after / ui / org / eclipse / jdt / internal / ui / fix / NullQuickFixes.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.ArrayList;
15 import java.util.Collection;
16 import java.util.HashSet;
17 import java.util.Hashtable;
18 import java.util.List;
19 import java.util.Map;
20 import java.util.Set;
21
22 import org.eclipse.swt.graphics.Image;
23
24 import org.eclipse.jdt.core.ICompilationUnit;
25 import org.eclipse.jdt.core.IJavaElement;
26 import org.eclipse.jdt.core.JavaCore;
27 import org.eclipse.jdt.core.compiler.IProblem;
28 import org.eclipse.jdt.core.dom.ASTNode;
29 import org.eclipse.jdt.core.dom.CompilationUnit;
30 import org.eclipse.jdt.core.dom.IBinding;
31 import org.eclipse.jdt.core.dom.IVariableBinding;
32 import org.eclipse.jdt.core.dom.SimpleName;
33 import org.eclipse.jdt.core.dom.VariableDeclaration;
34
35 import org.eclipse.jdt.internal.corext.dom.ASTNodes;
36 import org.eclipse.jdt.internal.corext.fix.CompilationUnitRewriteOperationsFix;
37 import org.eclipse.jdt.internal.corext.fix.CompilationUnitRewriteOperationsFix.CompilationUnitRewriteOperation;
38 import org.eclipse.jdt.internal.corext.util.JavaModelUtil;
39
40 import org.eclipse.jdt.ui.cleanup.ICleanUpFix;
41 import org.eclipse.jdt.ui.text.java.IInvocationContext;
42 import org.eclipse.jdt.ui.text.java.IProblemLocation;
43 import org.eclipse.jdt.ui.text.java.correction.ICommandAccess;
44
45 import org.eclipse.jdt.internal.ui.JavaPluginImages;
46 import org.eclipse.jdt.internal.ui.text.correction.ProblemLocation;
47 import org.eclipse.jdt.internal.ui.text.correction.proposals.FixCorrectionProposal;
48
49 /**
50  * Quick Fixes for null-annotation related problems.
51  */
52 public class NullQuickFixes {
53
54         /** Small adaptation just to make available the 'compilationUnit' passed at instantiation time. */
55         private static class MyCURewriteOperationsFix extends CompilationUnitRewriteOperationsFix {
56                 CompilationUnit cu;
57
58                 public MyCURewriteOperationsFix(String name, CompilationUnit compilationUnit, CompilationUnitRewriteOperation[] operations) {
59                         super(name, compilationUnit, operations);
60                         this.cu= compilationUnit;
61                 }
62         }
63
64         public static void addReturnAndArgumentTypeProposal(IInvocationContext context, IProblemLocation problem, Collection<ICommandAccess> proposals) {
65                 CompilationUnit astRoot= context.getASTRoot();
66                 ASTNode selectedNode= problem.getCoveringNode(astRoot);
67
68                 if (isComplainingAboutArgument(selectedNode) || isComplainingAboutReturn(selectedNode))
69                         addNullAnnotationInSignatureProposal(context, problem, proposals, false);
70         }
71
72         public static void addNullAnnotationInSignatureProposal(IInvocationContext context, IProblemLocation problem, Collection<ICommandAccess> proposals, boolean modifyOverridden) {
73                 MyCURewriteOperationsFix fix= createNullAnnotationInSignatureFix(context.getASTRoot(), problem, modifyOverridden);
74
75                 if (fix != null) {
76                         Image image= JavaPluginImages.get(JavaPluginImages.IMG_CORRECTION_CHANGE);
77                         Map<String, String> options= new Hashtable<String, String>();
78                         if (fix.cu != context.getASTRoot()) {
79                                 // workaround: adjust the unit to operate on, depending on the findings of RewriteOperations.createAddAnnotationOperation(..)
80                                 final CompilationUnit cu= fix.cu;
81                                 final IInvocationContext originalContext= context;
82                                 context= new IInvocationContext() {
83                                         public int getSelectionOffset() {
84                                                 return originalContext.getSelectionOffset();
85                                         }
86
87                                         public int getSelectionLength() {
88                                                 return originalContext.getSelectionLength();
89                                         }
90
91                                         public ASTNode getCoveringNode() {
92                                                 return originalContext.getCoveringNode();
93                                         }
94
95                                         public ASTNode getCoveredNode() {
96                                                 return originalContext.getCoveredNode();
97                                         }
98
99                                         public ICompilationUnit getCompilationUnit() {
100                                                 return (ICompilationUnit) cu.getJavaElement();
101                                         }
102
103                                         public CompilationUnit getASTRoot() {
104                                                 return cu;
105                                         }
106                                 };
107                         }
108                         int relevance= modifyOverridden ? 9 : 10; //raise local change above change in overridden method
109                         FixCorrectionProposal proposal= new FixCorrectionProposal(fix, new NullAnnotationsCleanUp(options, problem.getProblemId()), relevance, image, context);
110                         proposals.add(proposal);
111                 }
112         }
113
114         public static boolean isComplainingAboutArgument(ASTNode selectedNode) {
115                 if (!(selectedNode instanceof SimpleName))
116                         return false;
117                 SimpleName nameNode= (SimpleName) selectedNode;
118                 IBinding binding= nameNode.resolveBinding();
119                 if (binding.getKind() == IBinding.VARIABLE && ((IVariableBinding) binding).isParameter())
120                         return true;
121                 VariableDeclaration argDecl= (VariableDeclaration) ASTNodes.getParent(selectedNode, VariableDeclaration.class);
122                 if (argDecl != null)
123                         binding= argDecl.resolveBinding();
124                 if (binding.getKind() == IBinding.VARIABLE && ((IVariableBinding) binding).isParameter())
125                         return true;
126                 return false;
127         }
128
129         public static boolean isComplainingAboutReturn(ASTNode selectedNode) {
130                 return selectedNode.getParent().getNodeType() == ASTNode.RETURN_STATEMENT;
131         }
132
133         private static MyCURewriteOperationsFix createNullAnnotationInSignatureFix(CompilationUnit compilationUnit, IProblemLocation problem, boolean modifyOverridden) {
134                 String nullableAnnotationName= getNullableAnnotationName(compilationUnit.getJavaElement(), false);
135                 String nonNullAnnotationName= getNonNullAnnotationName(compilationUnit.getJavaElement(), false);
136                 String annotationToAdd= nullableAnnotationName;
137                 String annotationToRemove= nonNullAnnotationName;
138
139                 switch (problem.getProblemId()) {
140                         case IProblem.IllegalDefinitionToNonNullParameter:
141                         case IProblem.IllegalRedefinitionToNonNullParameter:
142                                 // case ParameterLackingNullableAnnotation: // never proposed with modifyOverridden
143                                 if (modifyOverridden) {
144                                         annotationToAdd= nonNullAnnotationName;
145                                         annotationToRemove= nullableAnnotationName;
146                                 }
147                                 break;
148                         case IProblem.ParameterLackingNonNullAnnotation:
149                         case IProblem.IllegalReturnNullityRedefinition:
150                                 if (!modifyOverridden) {
151                                         annotationToAdd= nonNullAnnotationName;
152                                         annotationToRemove= nullableAnnotationName;
153                                 }
154                                 break;
155                         case IProblem.RequiredNonNullButProvidedNull:
156                         case IProblem.RequiredNonNullButProvidedPotentialNull:
157                         case IProblem.RequiredNonNullButProvidedUnknown:
158                                 annotationToAdd= nonNullAnnotationName;
159                                 break;
160                 // all others propose to add @Nullable
161                 }
162
163                 // when performing one change at a time we can actually modify another CU than the current one:
164                 NullRewriteOperations.SignatureAnnotationRewriteOperation operation= NullRewriteOperations.createAddAnnotationOperation(compilationUnit, problem, annotationToAdd, annotationToRemove, null,
165                                 false/*thisUnitOnly*/, true/*allowRemove*/, modifyOverridden);
166                 if (operation == null)
167                         return null;
168
169                 return new MyCURewriteOperationsFix(operation.getMessage(), operation.getCompilationUnit(), // note that this uses the findings from createAddAnnotationOperation(..)
170                                 new NullRewriteOperations.SignatureAnnotationRewriteOperation[] { operation });
171         }
172
173         // Entry for NullAnnotationsCleanup:
174         public static ICleanUpFix createCleanUp(CompilationUnit compilationUnit, IProblemLocation[] locations, int problemID) {
175                 ICompilationUnit cu= (ICompilationUnit) compilationUnit.getJavaElement();
176                 if (!JavaModelUtil.is50OrHigher(cu.getJavaProject()))
177                         return null;
178
179                 List<CompilationUnitRewriteOperation> operations= new ArrayList<CompilationUnitRewriteOperation>();
180                 if (locations == null) {
181                         org.eclipse.jdt.core.compiler.IProblem[] problems= compilationUnit.getProblems();
182                         locations= new IProblemLocation[problems.length];
183                         for (int i= 0; i < problems.length; i++) {
184                                 if (problems[i].getID() == problemID)
185                                         locations[i]= new ProblemLocation(problems[i]);
186                         }
187                 }
188
189                 createAddNullAnnotationOperations(compilationUnit, locations, operations);
190                 if (operations.size() == 0)
191                         return null;
192                 CompilationUnitRewriteOperation[] operationsArray= operations.toArray(new CompilationUnitRewriteOperation[operations.size()]);
193                 return new MyCURewriteOperationsFix(NullFixMessages.QuickFixes_add_annotation_change_name, compilationUnit, operationsArray);
194         }
195
196         private static void createAddNullAnnotationOperations(CompilationUnit compilationUnit, IProblemLocation[] locations, List<CompilationUnitRewriteOperation> result) {
197                 String nullableAnnotationName= getNullableAnnotationName(compilationUnit.getJavaElement(), false);
198                 String nonNullAnnotationName= getNonNullAnnotationName(compilationUnit.getJavaElement(), false);
199                 Set<String> handledPositions= new HashSet<String>();
200                 for (int i= 0; i < locations.length; i++) {
201                         IProblemLocation problem= locations[i];
202                         if (problem == null)
203                                 continue; // problem was filtered out by createCleanUp()
204                         String annotationToAdd= nullableAnnotationName;
205                         String annotationToRemove= nonNullAnnotationName;
206                         switch (problem.getProblemId()) {
207                                 case IProblem.IllegalDefinitionToNonNullParameter:
208                                 case IProblem.IllegalRedefinitionToNonNullParameter:
209                                 case IProblem.ParameterLackingNonNullAnnotation:
210                                 case IProblem.IllegalReturnNullityRedefinition:
211                                         annotationToAdd= nonNullAnnotationName;
212                                         annotationToRemove= nullableAnnotationName;
213                                         break;
214                                 case IProblem.RequiredNonNullButProvidedNull:
215                                 case IProblem.RequiredNonNullButProvidedPotentialNull:
216                                 case IProblem.RequiredNonNullButProvidedUnknown:
217                                         annotationToAdd= nonNullAnnotationName;
218                                         break;
219                         // all others propose to add @Nullable
220                         }
221                         // when performing multiple changes we can only modify the one CU that the CleanUp infrastructure provides to the operation.
222                         CompilationUnitRewriteOperation fix= NullRewriteOperations.createAddAnnotationOperation(compilationUnit, problem, annotationToAdd, annotationToRemove, handledPositions,
223                                         true/*thisUnitOnly*/, false/*allowRemove*/, false/*modifyOverridden*/);
224                         if (fix != null)
225                                 result.add(fix);
226                 }
227         }
228
229 //      private static boolean isMissingNullAnnotationProblem(int id) {
230 //              return id == IProblem.RequiredNonNullButProvidedNull || id == IProblem.RequiredNonNullButProvidedPotentialNull || id == IProblem.IllegalReturnNullityRedefinition
231 //                              || mayIndicateParameterNullcheck(id);
232 //      }
233 //
234 //      private static boolean mayIndicateParameterNullcheck(int problemId) {
235 //              return problemId == IProblem.NonNullLocalVariableComparisonYieldsFalse || problemId == IProblem.RedundantNullCheckOnNonNullLocalVariable;
236 //      }
237
238         public static boolean hasExplicitNullAnnotation(ICompilationUnit compilationUnit, int offset) {
239 // FIXME(SH): check for existing annotations disabled due to lack of precision:
240 //                    should distinguish what is actually annotated (return? param? which?)
241 //              try {
242 //                      IJavaElement problemElement = compilationUnit.getElementAt(offset);
243 //                      if (problemElement.getElementType() == IJavaElement.METHOD) {
244 //                              IMethod method = (IMethod) problemElement;
245 //                              String nullable = getNullableAnnotationName(compilationUnit, true);
246 //                              String nonnull = getNonNullAnnotationName(compilationUnit, true);
247 //                              for (IAnnotation annotation : method.getAnnotations()) {
248 //                                      if (   annotation.getElementName().equals(nonnull)
249 //                                              || annotation.getElementName().equals(nullable))
250 //                                              return true;
251 //                              }
252 //                      }
253 //              } catch (JavaModelException jme) {
254 //                      /* nop */
255 //              }
256                 return false;
257         }
258
259         public static String getNullableAnnotationName(IJavaElement javaElement, boolean makeSimple) {
260                 String qualifiedName= javaElement.getJavaProject().getOption(JavaCore.COMPILER_NULLABLE_ANNOTATION_NAME, true);
261                 int lastDot;
262                 if (makeSimple && qualifiedName != null && (lastDot= qualifiedName.lastIndexOf('.')) != -1)
263                         return qualifiedName.substring(lastDot + 1);
264                 return qualifiedName;
265         }
266
267         public static String getNonNullAnnotationName(IJavaElement javaElement, boolean makeSimple) {
268                 String qualifiedName= javaElement.getJavaProject().getOption(JavaCore.COMPILER_NONNULL_ANNOTATION_NAME, true);
269                 int lastDot;
270                 if (makeSimple && qualifiedName != null && (lastDot= qualifiedName.lastIndexOf('.')) != -1)
271                         return qualifiedName.substring(lastDot + 1);
272                 return qualifiedName;
273         }
274 }