View | Details | Raw Unified | Return to bug 337977 | Differences between
and this patch

Collapse All | Expand All

(-)a/org.eclipse.jdt.ui/plugin.properties (+1 lines)
Lines 39-44 Link Here
39
spellingQuickFixProcessor=Spelling Quick Fix Processor
39
spellingQuickFixProcessor=Spelling Quick Fix Processor
40
defaultQuickAssistProcessor=Standard Quick Assist Processor
40
defaultQuickAssistProcessor=Standard Quick Assist Processor
41
advancedQuickAssistProcessor=Advanced Quick Assist Processor
41
advancedQuickAssistProcessor=Advanced Quick Assist Processor
42
nullQuickFixProcessor=Nullity Quick Fix Processor
42
43
43
javaCompletionProposalComputer= Java Completion Proposal Computers
44
javaCompletionProposalComputer= Java Completion Proposal Computers
44
javaCompletionProposalSorters= Java Completion Proposal Sorters
45
javaCompletionProposalSorters= Java Completion Proposal Sorters
(-)a/org.eclipse.jdt.ui/plugin.xml (+5 lines)
Lines 193-198 Link Here
193
                <markerType id="org.eclipse.jdt.ui.internal.spelling"/>
193
                <markerType id="org.eclipse.jdt.ui.internal.spelling"/>
194
	        </handledMarkerTypes>
194
	        </handledMarkerTypes>
195
      </quickFixProcessor>
195
      </quickFixProcessor>
196
      <quickFixProcessor
197
            name="%nullQuickFixProcessor"
198
            class="org.eclipse.jdt.internal.ui.fix.NullQuickFixes"
199
            id="org.eclipse.jdt.quickfix.NullQuickFixes">
200
      </quickFixProcessor>
196
   </extension>
201
   </extension>
197
   <extension
202
   <extension
198
         point="org.eclipse.jdt.ui.quickAssistProcessors">
203
         point="org.eclipse.jdt.ui.quickAssistProcessors">
(-)a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/fix/NullAnnotationsCleanUp.java (+159 lines)
Added Link Here
1
/*******************************************************************************
2
 * Copyright (c) 2011 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 - initial API and implementation 
10
 *******************************************************************************/
11
package org.eclipse.jdt.internal.ui.fix;
12
13
import java.util.ArrayList;
14
import java.util.Hashtable;
15
import java.util.List;
16
import java.util.Map;
17
18
import org.eclipse.core.runtime.CoreException;
19
import org.eclipse.jdt.core.ICompilationUnit;
20
import org.eclipse.jdt.core.JavaCore;
21
import org.eclipse.jdt.core.compiler.IProblem;
22
import org.eclipse.jdt.core.dom.CompilationUnit;
23
import org.eclipse.jdt.internal.ui.fix.AbstractMultiFix;
24
import org.eclipse.jdt.ui.cleanup.CleanUpRequirements;
25
import org.eclipse.jdt.ui.cleanup.ICleanUpFix;
26
import org.eclipse.jdt.ui.text.java.IProblemLocation;
27
28
/**
29
 * Cleanup for adding required null annotations.
30
 * 
31
 * Crafted after the lead of Java50CleanUp
32
 * 
33
 * @author stephan
34
 */
35
public class NullAnnotationsCleanUp extends AbstractMultiFix {
36
37
	private NullQuickFixes master;
38
	private int handledProblemID;
39
40
	public NullAnnotationsCleanUp(Map<String, String> options, NullQuickFixes quickFixes, int handledProblemID) {
41
		super(options);
42
		this.master = quickFixes;
43
		this.handledProblemID = handledProblemID;
44
	}
45
46
	/**
47
	 * {@inheritDoc}
48
	 */
49
	@Override
50
	public CleanUpRequirements getRequirements() {
51
		Map<String, String> requiredOptions= getRequiredOptions();
52
		return new CleanUpRequirements(true, false, false, requiredOptions);
53
	}
54
55
56
	/**
57
	 * {@inheritDoc}
58
	 */
59
	@Override
60
	protected ICleanUpFix createFix(CompilationUnit compilationUnit) throws CoreException {
61
		return this.createFix(compilationUnit, null);
62
	}
63
64
	/**
65
	 * {@inheritDoc}
66
	 */
67
	@Override
68
	protected ICleanUpFix createFix(CompilationUnit compilationUnit, IProblemLocation[] problems) throws CoreException {
69
		if (compilationUnit == null)
70
			return null;
71
		IProblemLocation[] locations = null;
72
		ArrayList<IProblemLocation> filteredLocations = new ArrayList<IProblemLocation>();
73
		if (problems != null) {
74
			for (int i = 0; i < problems.length; i++) {
75
				if (problems[i].getProblemId() == this.handledProblemID)
76
					filteredLocations.add(problems[i]);
77
			}
78
			locations = filteredLocations.toArray(new IProblemLocation[filteredLocations.size()]);
79
		}
80
		return this.master.createCleanUp(compilationUnit, locations, this.handledProblemID);
81
	}
82
83
	private Map<String, String> getRequiredOptions() {
84
		Map<String, String> result= new Hashtable<String, String>();
85
		// TODO(SH): might set depending on this.handledProblemID, not sure about the benefit
86
		result.put(JavaCore.COMPILER_PB_NULL_SPECIFICATION_VIOLATION, JavaCore.WARNING);
87
		result.put(JavaCore.COMPILER_PB_REDUNDANT_NULL_CHECK, JavaCore.WARNING);
88
		return result;
89
	}
90
91
	/**
92
	 * {@inheritDoc}
93
	 */
94
	@Override
95
	public String[] getStepDescriptions() {
96
		List<String> result= new ArrayList<String>();
97
		switch (this.handledProblemID) {
98
		case IProblem.NonNullLocalVariableComparisonYieldsFalse:
99
		case IProblem.RedundantNullCheckOnNonNullLocalVariable:
100
		case IProblem.RequiredNonNullButProvidedNull:
101
		case IProblem.RequiredNonNullButProvidedPotentialNull:
102
		case IProblem.RequiredNonNullButProvidedUnknown:
103
		case IProblem.ParameterLackingNullableAnnotation:
104
			result.add(NullFixMessages.NullAnnotationsCleanUp_add_nullable_annotation);
105
			break;
106
		case IProblem.IllegalDefinitionToNonNullParameter:
107
		case IProblem.IllegalRedefinitionToNonNullParameter:
108
		case IProblem.ParameterLackingNonNullAnnotation:
109
			result.add(NullFixMessages.NullAnnotationsCleanUp_add_nonnull_annotation);
110
			break;	
111
		}
112
		return result.toArray(new String[result.size()]);
113
	}
114
115
	/**
116
	 * {@inheritDoc}
117
	 */
118
	@Override
119
	public String getPreview() {
120
		// not used when not provided as a true cleanup(?)
121
		return "No preview available"; //$NON-NLS-1$
122
	}
123
124
	/**
125
	 * {@inheritDoc}
126
	 */
127
	public boolean canFix(ICompilationUnit compilationUnit, IProblemLocation problem) {
128
		int id= problem.getProblemId();
129
		
130
		if (id == this.handledProblemID) {
131
			// FIXME search specifically: return param (which??)
132
//			if (QuickFixes.hasExplicitNullnessAnnotation(compilationUnit, problem.getOffset()))
133
//				return false;
134
			return true;			
135
		}
136
137
		return false;
138
	}
139
140
141
	/**
142
	 * {@inheritDoc}
143
	 */
144
	@Override
145
	public int computeNumberOfFixes(CompilationUnit compilationUnit) {
146
		int result= 0;
147
		
148
		IProblem[] problems= compilationUnit.getProblems();
149
		for (int i= 0; i < problems.length; i++) {
150
			int id= problems[i].getID();
151
			if (id == this.handledProblemID) {
152
				// FIXME search specifically: return param (which??)
153
//				if (!QuickFixes.hasExplicitNullnessAnnotation(compilationUnit, problems[i].getSourceStart()))
154
					result++;
155
			}
156
		}
157
		return result;
158
	}
159
}
(-)a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/fix/NullFixMessages.java (+41 lines)
Added Link Here
1
/*******************************************************************************
2
 * Copyright (c) 2011 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 - initial API and implementation 
10
 *******************************************************************************/
11
package org.eclipse.jdt.internal.ui.fix;
12
13
import org.eclipse.osgi.util.NLS;
14
15
public class NullFixMessages extends NLS {
16
	private static final String BUNDLE_NAME = "org.eclipse.jdt.internal.ui.fix.NullFixMessages"; //$NON-NLS-1$
17
	
18
	
19
	public static String NullAnnotationsCleanUp_add_nullable_annotation;
20
	public static String NullAnnotationsCleanUp_add_nonnull_annotation;
21
	
22
	public static String QuickFixes_add_annotation_change_name;
23
	
24
	public static String QuickFixes_declare_method_parameter_nullness;
25
26
	public static String QuickFixes_declare_method_return_nullness;
27
28
29
	public static String QuickFixes_declare_overridden_parameter_as_nonnull;
30
31
32
	public static String QuickFixes_declare_overridden_return_as_nullable;
33
34
	static {
35
		// initialize resource bundle
36
		NLS.initializeMessages(BUNDLE_NAME, NullFixMessages.class);
37
	}
38
39
	private NullFixMessages() {
40
	}
41
}
(-)a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/fix/NullFixMessages.properties (+7 lines)
Added Link Here
1
NullAnnotationsCleanUp_add_nullable_annotation=Add missing @Nullable annotation
2
NullAnnotationsCleanUp_add_nonnull_annotation=Add missing @NonNull annotation
3
QuickFixes_add_annotation_change_name=Add Annotations
4
QuickFixes_declare_method_parameter_nullness=Declare method parameter as @{0}
5
QuickFixes_declare_method_return_nullness=Declare method return as @{0}
6
QuickFixes_declare_overridden_parameter_as_nonnull=Adjust overridden method from {1}, mark parameter as @{0}
7
QuickFixes_declare_overridden_return_as_nullable=Adjust overridden method from {1}, mark as returning @{0}
(-)a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/fix/NullQuickFixes.java (+338 lines)
Added Link Here
1
/*******************************************************************************
2
 * Copyright (c) 2011 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 - initial API and implementation 
10
 *******************************************************************************/
11
package org.eclipse.jdt.internal.ui.fix;
12
13
import java.util.ArrayList;
14
import java.util.Collection;
15
import java.util.HashSet;
16
import java.util.Hashtable;
17
import java.util.List;
18
import java.util.Map;
19
import java.util.Set;
20
21
import org.eclipse.swt.graphics.Image;
22
23
import org.eclipse.core.runtime.CoreException;
24
25
import org.eclipse.jdt.core.ICompilationUnit;
26
import org.eclipse.jdt.core.IJavaElement;
27
import org.eclipse.jdt.core.JavaCore;
28
import org.eclipse.jdt.core.compiler.IProblem;
29
import org.eclipse.jdt.core.dom.ASTNode;
30
import org.eclipse.jdt.core.dom.CompilationUnit;
31
import org.eclipse.jdt.core.dom.IBinding;
32
import org.eclipse.jdt.core.dom.IVariableBinding;
33
import org.eclipse.jdt.core.dom.SimpleName;
34
import org.eclipse.jdt.core.dom.VariableDeclaration;
35
36
import org.eclipse.jdt.internal.corext.dom.ASTNodes;
37
import org.eclipse.jdt.internal.corext.fix.CompilationUnitRewriteOperationsFix;
38
import org.eclipse.jdt.internal.corext.fix.CompilationUnitRewriteOperationsFix.CompilationUnitRewriteOperation;
39
import org.eclipse.jdt.internal.corext.util.JavaModelUtil;
40
41
import org.eclipse.jdt.ui.cleanup.ICleanUpFix;
42
import org.eclipse.jdt.ui.text.java.IInvocationContext;
43
import org.eclipse.jdt.ui.text.java.IJavaCompletionProposal;
44
import org.eclipse.jdt.ui.text.java.IProblemLocation;
45
46
import org.eclipse.jdt.internal.ui.JavaPluginImages;
47
import org.eclipse.jdt.internal.ui.text.correction.ProblemLocation;
48
import org.eclipse.jdt.internal.ui.text.correction.proposals.FixCorrectionProposal;
49
50
/**
51
 * Quickfixes for null-annotation related problems.
52
 * 
53
 * @author stephan
54
 */
55
public class NullQuickFixes implements org.eclipse.jdt.ui.text.java.IQuickFixProcessor {
56
57
	/** Small adaptation just to make available the 'compilationUnit' passed at instantiation time. */
58
	static class MyCURewriteOperationsFix extends CompilationUnitRewriteOperationsFix {
59
		CompilationUnit cu;
60
		public MyCURewriteOperationsFix(String name, CompilationUnit compilationUnit, CompilationUnitRewriteOperation[] operations) 
61
		{
62
			super(name, compilationUnit, operations);
63
			this.cu = compilationUnit;
64
		}
65
	}
66
67
	public boolean hasCorrections(ICompilationUnit cu, int problemId) {
68
		switch (problemId) {
69
		case IProblem.RequiredNonNullButProvidedNull:
70
		case IProblem.RequiredNonNullButProvidedPotentialNull:
71
		case IProblem.RequiredNonNullButProvidedUnknown:
72
		case IProblem.IllegalReturnNullityRedefinition:
73
		case IProblem.IllegalRedefinitionToNonNullParameter:
74
		case IProblem.IllegalDefinitionToNonNullParameter:
75
		case IProblem.ParameterLackingNonNullAnnotation:
76
		case IProblem.ParameterLackingNullableAnnotation:
77
		case IProblem.NonNullLocalVariableComparisonYieldsFalse:
78
		case IProblem.RedundantNullCheckOnNonNullLocalVariable:
79
				return true;
80
		default:
81
			return false;
82
		}
83
	}
84
	
85
	/* (non-Javadoc)
86
	 * @see IAssistProcessor#getCorrections(org.eclipse.jdt.internal.ui.text.correction.IAssistContext, org.eclipse.jdt.internal.ui.text.correction.IProblemLocation[])
87
	 */
88
	public IJavaCompletionProposal[] getCorrections(IInvocationContext context, IProblemLocation[] locations) throws CoreException {
89
		if (locations == null || locations.length == 0) {
90
			return null;
91
		}
92
93
		HashSet<Integer> handledProblems= new HashSet<Integer>(locations.length);
94
		ArrayList<IJavaCompletionProposal> resultingCollections= new ArrayList<IJavaCompletionProposal>();
95
		for (int i= 0; i < locations.length; i++) {
96
			IProblemLocation curr= locations[i];
97
			Integer id= new Integer(curr.getProblemId());
98
			if (handledProblems.add(id)) {
99
				process(context, curr, resultingCollections);
100
			}
101
		}
102
		return resultingCollections.toArray(new IJavaCompletionProposal[resultingCollections.size()]);
103
	}
104
105
	void process(IInvocationContext context, IProblemLocation problem, Collection<IJavaCompletionProposal> proposals) {
106
		int id= problem.getProblemId();
107
		if (id == 0) { // no proposals for none-problem locations
108
			return;
109
		}
110
		CompilationUnit astRoot= context.getASTRoot();
111
		ASTNode selectedNode= problem.getCoveringNode(astRoot);
112
113
		switch (id) {
114
		case IProblem.IllegalReturnNullityRedefinition:
115
		case IProblem.IllegalDefinitionToNonNullParameter:
116
		case IProblem.IllegalRedefinitionToNonNullParameter:
117
			addNullAnnotationInSignatureProposal(context, problem, proposals, false);
118
			addNullAnnotationInSignatureProposal(context, problem, proposals, true);
119
			break;
120
		case IProblem.RequiredNonNullButProvidedNull:
121
		case IProblem.RequiredNonNullButProvidedPotentialNull:
122
		case IProblem.RequiredNonNullButProvidedUnknown:
123
		case IProblem.ParameterLackingNonNullAnnotation:
124
		case IProblem.ParameterLackingNullableAnnotation:
125
		case IProblem.NonNullLocalVariableComparisonYieldsFalse:
126
		case IProblem.RedundantNullCheckOnNonNullLocalVariable:
127
			if (isComplainingAboutArgument(selectedNode)
128
				|| isComplainingAboutReturn(selectedNode))
129
				addNullAnnotationInSignatureProposal(context, problem, proposals, false);
130
			break;
131
		}
132
	}
133
		
134
	@SuppressWarnings("unchecked")
135
	void addNullAnnotationInSignatureProposal(IInvocationContext context,
136
											  IProblemLocation problem,
137
											  @SuppressWarnings("rawtypes") Collection proposals,
138
											  boolean modifyOverridden)
139
	{
140
		MyCURewriteOperationsFix fix= createNullAnnotationInSignatureFix(context.getASTRoot(), problem, modifyOverridden);
141
		
142
		if (fix != null) {
143
			Image image= JavaPluginImages.get(JavaPluginImages.IMG_CORRECTION_CHANGE);
144
			Map<String, String> options= new Hashtable<String, String>();
145
			if (fix.cu != context.getASTRoot()) {
146
				// workaround: adjust the unit to operate on, depending on the findings of RewriteOperations.createAddAnnotationOperation(..)
147
				final CompilationUnit cu = fix.cu;
148
				final IInvocationContext originalContext = context;
149
				context = new IInvocationContext() {
150
					public int getSelectionOffset() {
151
						return originalContext.getSelectionOffset();
152
					}					
153
					public int getSelectionLength() {
154
						return originalContext.getSelectionLength();
155
					}
156
					public ASTNode getCoveringNode() {
157
						return originalContext.getCoveringNode();
158
					}					
159
					public ASTNode getCoveredNode() {
160
						return originalContext.getCoveredNode();
161
					}					
162
					public ICompilationUnit getCompilationUnit() {
163
						return (ICompilationUnit) cu.getJavaElement();
164
					}
165
					
166
					public CompilationUnit getASTRoot() {
167
						return cu;
168
					}
169
				};
170
			}
171
			int relevance = modifyOverridden ? 15 : 20; // TODO(SH): insert suitable values, just raise local change above change in overridden method
172
			FixCorrectionProposal proposal= new FixCorrectionProposal(fix, new NullAnnotationsCleanUp(options, this, problem.getProblemId()), relevance, image, context);
173
			proposals.add(proposal);
174
		}		
175
	}
176
	
177
	boolean isComplainingAboutArgument(ASTNode selectedNode) {
178
		if (!(selectedNode instanceof SimpleName)) {
179
			return false;
180
		}
181
		SimpleName nameNode= (SimpleName) selectedNode;
182
		IBinding binding = nameNode.resolveBinding();
183
		if (binding.getKind() == IBinding.VARIABLE && ((IVariableBinding) binding).isParameter())
184
			return true;
185
		VariableDeclaration argDecl = (VariableDeclaration) ASTNodes.getParent(selectedNode, VariableDeclaration.class);
186
		if (argDecl != null)
187
			binding = argDecl.resolveBinding();
188
		if (binding.getKind() == IBinding.VARIABLE && ((IVariableBinding) binding).isParameter())
189
			return true;
190
		return false;
191
	}
192
	
193
	boolean isComplainingAboutReturn(ASTNode selectedNode) {
194
		return selectedNode.getParent().getNodeType() == ASTNode.RETURN_STATEMENT;
195
	}
196
197
	MyCURewriteOperationsFix createNullAnnotationInSignatureFix(CompilationUnit compilationUnit, IProblemLocation problem, boolean modifyOverridden)
198
	{
199
		String nullableAnnotationName = getNullableAnnotationName(compilationUnit.getJavaElement(), false);
200
		String nonNullAnnotationName = getNonNullAnnotationName(compilationUnit.getJavaElement(), false);
201
		String annotationToAdd = nullableAnnotationName;
202
		String annotationToRemove = nonNullAnnotationName;
203
		
204
		switch (problem.getProblemId()) {
205
		case IProblem.IllegalDefinitionToNonNullParameter:
206
		case IProblem.IllegalRedefinitionToNonNullParameter:
207
		// case ParameterLackingNullableAnnotation: // never proposed with modifyOverridden
208
			if (modifyOverridden) {
209
				annotationToAdd = nonNullAnnotationName;
210
				annotationToRemove = nullableAnnotationName;
211
			}
212
			break;
213
		case IProblem.ParameterLackingNonNullAnnotation:
214
		case IProblem.IllegalReturnNullityRedefinition:
215
			if (!modifyOverridden) {
216
				annotationToAdd = nonNullAnnotationName;
217
				annotationToRemove = nullableAnnotationName;
218
			}
219
			break;
220
		// all others propose to add @Nullable
221
		}
222
		
223
		// when performing one change at a time we can actually modify another CU than the current one:
224
		NullRewriteOperations.SignatureAnnotationRewriteOperation operation = 
225
			NullRewriteOperations.createAddAnnotationOperation(
226
				compilationUnit, problem, annotationToAdd, annotationToRemove, null,
227
				false/*thisUnitOnly*/, true/*allowRemove*/, modifyOverridden);
228
		if (operation == null)
229
			return null;
230
		
231
		return new MyCURewriteOperationsFix(operation.getMessage(),
232
											operation.getCompilationUnit(), // note that this uses the findings from createAddAnnotationOperation(..)
233
											new NullRewriteOperations.SignatureAnnotationRewriteOperation[] {operation});
234
	}
235
236
	// Entry for NullAnnotationsCleanup:
237
	public ICleanUpFix createCleanUp(CompilationUnit compilationUnit, IProblemLocation[] locations, int problemID)
238
	{
239
		
240
		ICompilationUnit cu= (ICompilationUnit)compilationUnit.getJavaElement();
241
		if (!JavaModelUtil.is50OrHigher(cu.getJavaProject()))
242
			return null;
243
		
244
		List<CompilationUnitRewriteOperation> operations= new ArrayList<CompilationUnitRewriteOperation>();
245
		
246
		if (locations == null) {
247
			org.eclipse.jdt.core.compiler.IProblem[] problems= compilationUnit.getProblems();
248
			locations= new IProblemLocation[problems.length];
249
			for (int i= 0; i < problems.length; i++) {
250
				if (problems[i].getID() == problemID)
251
					locations[i]= new ProblemLocation(problems[i]);
252
			}
253
		}
254
		
255
		createAddNullAnnotationOperations(compilationUnit, locations, operations);
256
		
257
		if (operations.size() == 0)
258
			return null;
259
		
260
		CompilationUnitRewriteOperation[] operationsArray= operations.toArray(new CompilationUnitRewriteOperation[operations.size()]);
261
		return new MyCURewriteOperationsFix(NullFixMessages.QuickFixes_add_annotation_change_name, compilationUnit, operationsArray);
262
	}
263
264
	@SuppressWarnings({ "rawtypes", "unchecked" })
265
	void createAddNullAnnotationOperations(CompilationUnit compilationUnit, IProblemLocation[] locations, List result) {
266
		String nullableAnnotationName = getNullableAnnotationName(compilationUnit.getJavaElement(), false);
267
		String nonNullAnnotationName = getNonNullAnnotationName(compilationUnit.getJavaElement(), false);
268
		Set<String> handledPositions = new HashSet<String>();
269
		for (int i= 0; i < locations.length; i++) {
270
			IProblemLocation problem= locations[i];
271
			if (problem == null) continue; // problem was filtered out by createCleanUp()
272
			String annotationToAdd = nullableAnnotationName;
273
			String annotationToRemove = nonNullAnnotationName;
274
			switch (problem.getProblemId()) {
275
			case IProblem.IllegalDefinitionToNonNullParameter:
276
			case IProblem.IllegalRedefinitionToNonNullParameter:
277
			case IProblem.ParameterLackingNonNullAnnotation:
278
			case IProblem.IllegalReturnNullityRedefinition:
279
				annotationToAdd = nonNullAnnotationName;
280
				annotationToRemove = nullableAnnotationName;
281
				// all others propose to add @Nullable
282
			}
283
			// when performing multiple changes we can only modify the one CU that the CleanUp infrastructure provides to the operation.
284
			CompilationUnitRewriteOperation fix = NullRewriteOperations.createAddAnnotationOperation(
285
						compilationUnit, problem, annotationToAdd, annotationToRemove,
286
						handledPositions, true/*thisUnitOnly*/, false/*allowRemove*/, false/*modifyOverridden*/);
287
			if (fix != null)
288
				result.add(fix);
289
		}
290
	}
291
	
292
	public static boolean isMissingNullAnnotationProblem(int id) {
293
		return id == IProblem.RequiredNonNullButProvidedNull || id == IProblem.RequiredNonNullButProvidedPotentialNull 
294
				|| id == IProblem.IllegalReturnNullityRedefinition
295
				|| mayIndicateParameterNullcheck(id);
296
	}
297
	
298
	public static boolean mayIndicateParameterNullcheck(int problemId) {
299
		return problemId == IProblem.NonNullLocalVariableComparisonYieldsFalse || problemId == IProblem.RedundantNullCheckOnNonNullLocalVariable;
300
	}
301
	
302
	public static boolean hasExplicitNullAnnotation(ICompilationUnit compilationUnit, int offset) {
303
// FIXME(SH): check for existing annotations disabled due to lack of precision:
304
//		      should distinguish what is actually annotated (return? param? which?)
305
//		try {
306
//			IJavaElement problemElement = compilationUnit.getElementAt(offset);
307
//			if (problemElement.getElementType() == IJavaElement.METHOD) {
308
//				IMethod method = (IMethod) problemElement;
309
//				String nullable = getNullableAnnotationName(compilationUnit, true);
310
//				String nonnull = getNonNullAnnotationName(compilationUnit, true);
311
//				for (IAnnotation annotation : method.getAnnotations()) {
312
//					if (   annotation.getElementName().equals(nonnull)
313
//						|| annotation.getElementName().equals(nullable))
314
//						return true;
315
//				}
316
//			}
317
//		} catch (JavaModelException jme) {
318
//			/* nop */
319
//		}
320
		return false;
321
	}
322
323
	public static String getNullableAnnotationName(IJavaElement javaElement, boolean makeSimple) {
324
		String qualifiedName = javaElement.getJavaProject().getOption(JavaCore.COMPILER_NULLABLE_ANNOTATION_NAME, true);
325
		int lastDot;
326
		if (makeSimple && qualifiedName != null && (lastDot = qualifiedName.lastIndexOf('.')) != -1)
327
			return qualifiedName.substring(lastDot+1);
328
		return qualifiedName;
329
	}
330
331
	public static String getNonNullAnnotationName(IJavaElement javaElement, boolean makeSimple) {
332
		String qualifiedName = javaElement.getJavaProject().getOption(JavaCore.COMPILER_NONNULL_ANNOTATION_NAME, true);
333
		int lastDot;
334
		if (makeSimple && qualifiedName != null && (lastDot = qualifiedName.lastIndexOf('.')) != -1)
335
			return qualifiedName.substring(lastDot+1);
336
		return qualifiedName;
337
	}
338
}
(-)a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/fix/NullRewriteOperations.java (+536 lines)
Added Link Here
1
/*******************************************************************************
2
 * Copyright (c) 2011 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 - initial API and implementation 
10
 *******************************************************************************/
11
package org.eclipse.jdt.internal.ui.fix;
12
13
import java.util.List;
14
import java.util.Set;
15
16
import org.eclipse.core.runtime.CoreException;
17
import org.eclipse.jdt.core.ICompilationUnit;
18
import org.eclipse.jdt.core.JavaModelException;
19
import org.eclipse.jdt.core.compiler.IProblem;
20
import org.eclipse.jdt.core.dom.AST;
21
import org.eclipse.jdt.core.dom.ASTNode;
22
import org.eclipse.jdt.core.dom.Annotation;
23
import org.eclipse.jdt.core.dom.BodyDeclaration;
24
import org.eclipse.jdt.core.dom.CompilationUnit;
25
import org.eclipse.jdt.core.dom.IBinding;
26
import org.eclipse.jdt.core.dom.IExtendedModifier;
27
import org.eclipse.jdt.core.dom.IMethodBinding;
28
import org.eclipse.jdt.core.dom.ITypeBinding;
29
import org.eclipse.jdt.core.dom.IVariableBinding;
30
import org.eclipse.jdt.core.dom.MarkerAnnotation;
31
import org.eclipse.jdt.core.dom.MethodDeclaration;
32
import org.eclipse.jdt.core.dom.MethodInvocation;
33
import org.eclipse.jdt.core.dom.Name;
34
import org.eclipse.jdt.core.dom.SimpleName;
35
import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
36
import org.eclipse.jdt.core.dom.VariableDeclaration;
37
import org.eclipse.jdt.core.dom.rewrite.ImportRewrite;
38
import org.eclipse.jdt.core.dom.rewrite.ListRewrite;
39
import org.eclipse.jdt.internal.corext.dom.ASTNodes;
40
import org.eclipse.jdt.internal.corext.dom.Bindings;
41
import org.eclipse.jdt.internal.corext.fix.CompilationUnitRewriteOperationsFix.CompilationUnitRewriteOperation;
42
import org.eclipse.jdt.internal.corext.fix.LinkedProposalModel;
43
import org.eclipse.jdt.internal.corext.refactoring.structure.CompilationUnitRewrite;
44
import org.eclipse.jdt.internal.corext.util.JavaModelUtil;
45
import org.eclipse.jdt.internal.corext.util.Messages;
46
import org.eclipse.jdt.internal.ui.text.correction.ASTResolving;
47
import org.eclipse.jdt.internal.ui.viewsupport.BasicElementLabels;
48
import org.eclipse.jdt.ui.text.java.IProblemLocation;
49
import org.eclipse.text.edits.TextEditGroup;
50
51
public class NullRewriteOperations {
52
53
	static abstract class SignatureAnnotationRewriteOperation extends CompilationUnitRewriteOperation {
54
		String fAnnotationToAdd;
55
		String fAnnotationToRemove;
56
		boolean fAllowRemove;
57
		CompilationUnit fUnit;
58
		
59
		protected String fKey;
60
		protected String fMessage; 
61
		
62
		/** A globally unique key that identifies the position being annotated (for avoiding double annotations). */
63
		public String getKey() { return this.fKey; }
64
65
		public CompilationUnit getCompilationUnit() {
66
			return fUnit;
67
		}
68
		
69
70
		boolean checkExisting(@SuppressWarnings("rawtypes") List existingModifiers, 
71
							  ListRewrite listRewrite, 
72
							  TextEditGroup editGroup) 
73
		{
74
			for (Object mod : existingModifiers) {
75
				if (mod instanceof MarkerAnnotation) {
76
					MarkerAnnotation annotation = (MarkerAnnotation) mod;
77
					String existingName = annotation.getTypeName().getFullyQualifiedName();
78
					int lastDot = fAnnotationToRemove.lastIndexOf('.');
79
					if (   existingName.equals(fAnnotationToRemove)
80
						|| (lastDot != -1 && fAnnotationToRemove.substring(lastDot+1).equals(existingName)))
81
					{
82
						if (!fAllowRemove)
83
							return false; // veto this change
84
						listRewrite.remove(annotation, editGroup);
85
						return true; 
86
					}
87
					// paranoia: check if by accident the annotation is already present (shouldn't happen):
88
					lastDot = fAnnotationToAdd.lastIndexOf('.');
89
					if (   existingName.equals(fAnnotationToAdd)
90
						|| (lastDot != -1 && fAnnotationToAdd.substring(lastDot+1).equals(existingName)))
91
					{
92
						return false; // already present
93
					}					
94
				}
95
			}
96
			return true;
97
		}
98
99
		public String getMessage() {
100
			return fMessage;
101
		}
102
	}
103
	
104
	/**
105
	 * Rewrite operation that inserts an annotation into a method signature.
106
	 * 
107
	 * Crafted after the lead of Java50Fix.AnnotationRewriteOperation
108
	 * @author stephan
109
	 */
110
	static class ReturnAnnotationRewriteOperation extends SignatureAnnotationRewriteOperation {
111
112
		private final BodyDeclaration fBodyDeclaration;
113
114
		ReturnAnnotationRewriteOperation(CompilationUnit unit,
115
										 MethodDeclaration method,
116
										 String annotationToAdd,
117
										 String annotationToRemove,
118
										 boolean allowRemove,
119
										 String message)
120
		{
121
			fUnit = unit;
122
			fKey= method.resolveBinding().getKey()+"<return>"; //$NON-NLS-1$
123
			fBodyDeclaration= method;
124
			fAnnotationToAdd= annotationToAdd;
125
			fAnnotationToRemove= annotationToRemove;
126
			fAllowRemove= allowRemove;
127
			fMessage = message;
128
		}
129
130
		@Override
131
		public void rewriteAST(CompilationUnitRewrite cuRewrite, LinkedProposalModel model) throws CoreException {
132
			AST ast= cuRewrite.getRoot().getAST();
133
			ListRewrite listRewrite= cuRewrite.getASTRewrite().getListRewrite(fBodyDeclaration, fBodyDeclaration.getModifiersProperty());
134
			TextEditGroup group= createTextEditGroup(fMessage, cuRewrite);
135
			if (!checkExisting(fBodyDeclaration.modifiers(), listRewrite, group))
136
				return;
137
			Annotation newAnnotation= ast.newMarkerAnnotation();
138
			ImportRewrite importRewrite = cuRewrite.getImportRewrite();
139
			String resolvableName = importRewrite.addImport(fAnnotationToAdd);
140
			newAnnotation.setTypeName(ast.newName(resolvableName));
141
			listRewrite.insertLast(newAnnotation, group); // null annotation is last modifier, directly preceding the return type
142
		}
143
	}
144
	
145
	static class ParameterAnnotationRewriteOperation extends SignatureAnnotationRewriteOperation {
146
147
		private SingleVariableDeclaration fArgument;
148
149
		ParameterAnnotationRewriteOperation(CompilationUnit unit,
150
											MethodDeclaration method,
151
											String annotationToAdd,
152
											String annotationToRemove,
153
											String paramName,
154
											boolean allowRemove,
155
											String message)
156
		{
157
			fUnit= unit;
158
			fKey= method.resolveBinding().getKey();
159
			fAnnotationToAdd= annotationToAdd;
160
			fAnnotationToRemove= annotationToRemove;
161
			fAllowRemove= allowRemove;
162
			fMessage = message;
163
			for (Object param : method.parameters()) {
164
				SingleVariableDeclaration argument = (SingleVariableDeclaration) param;
165
				if (argument.getName().getIdentifier().equals(paramName)) {
166
					fArgument= argument;
167
					fKey += argument.getName().getIdentifier();
168
					return;
169
				}
170
			}
171
			// shouldn't happen, we've checked that paramName indeed denotes a parameter.
172
			throw new RuntimeException("Argument "+paramName+" not found in method "+method.getName().getIdentifier()); //$NON-NLS-1$ //$NON-NLS-2$
173
		}
174
175
		ParameterAnnotationRewriteOperation(CompilationUnit unit,
176
											MethodDeclaration method,
177
											String annotationToAdd,
178
											String annotationToRemove,
179
											int paramIdx,
180
											boolean allowRemove,
181
											String message)
182
		{
183
			fUnit= unit;
184
			fKey= method.resolveBinding().getKey();
185
			fAnnotationToAdd= annotationToAdd;
186
			fAnnotationToRemove= annotationToRemove;
187
			fAllowRemove= allowRemove;
188
			fArgument = (SingleVariableDeclaration) method.parameters().get(paramIdx);
189
			fKey += fArgument.getName().getIdentifier();
190
			fMessage = message;
191
		}
192
193
		@Override
194
		public void rewriteAST(CompilationUnitRewrite cuRewrite, LinkedProposalModel linkedModel) throws CoreException {
195
			AST ast= cuRewrite.getRoot().getAST();
196
			ListRewrite listRewrite= cuRewrite.getASTRewrite().getListRewrite(fArgument, SingleVariableDeclaration.MODIFIERS2_PROPERTY);
197
			TextEditGroup group= createTextEditGroup(fMessage, cuRewrite);
198
			if (!checkExisting(fArgument.modifiers(), listRewrite, group))
199
				return;
200
			Annotation newAnnotation= ast.newMarkerAnnotation();
201
			ImportRewrite importRewrite = cuRewrite.getImportRewrite();
202
			String resolvableName = importRewrite.addImport(fAnnotationToAdd);
203
			newAnnotation.setTypeName(ast.newName(resolvableName));
204
			listRewrite.insertLast(newAnnotation, group); // null annotation is last modifier, directly preceding the type
205
		}
206
	}
207
208
	// Entry for QuickFixes:
209
	public static SignatureAnnotationRewriteOperation createAddAnnotationOperation(CompilationUnit compilationUnit, 
210
																				   IProblemLocation problem, 
211
																				   String annotationToAdd, 
212
																				   String annotationToRemove, 
213
																				   Set<String> handledPositions,
214
																				   boolean thisUnitOnly, 
215
																				   boolean allowRemove,
216
																				   boolean modifyOverridden) 
217
	{
218
		SignatureAnnotationRewriteOperation result = modifyOverridden
219
			? createAddAnnotationToOverriddenOperation(compilationUnit, problem, annotationToAdd, annotationToRemove,
220
															handledPositions, thisUnitOnly, allowRemove)
221
			: createAddAnnotationOperation(compilationUnit, problem, annotationToAdd, annotationToRemove, 
222
															handledPositions, thisUnitOnly, allowRemove);
223
		if (handledPositions != null && result != null) {
224
			if (handledPositions.contains(result.getKey()))
225
				return null;
226
			handledPositions.add(result.getKey());
227
		}
228
		return result;
229
	}
230
	static SignatureAnnotationRewriteOperation createAddAnnotationOperation(CompilationUnit compilationUnit, 
231
																				   IProblemLocation problem, 
232
																				   String annotationToAdd, 
233
																				   String annotationToRemove, 
234
																				   Set<String> handledPositions,
235
																				   boolean thisUnitOnly, 
236
																				   boolean allowRemove) 
237
	{
238
		ICompilationUnit cu= (ICompilationUnit)compilationUnit.getJavaElement();
239
		if (!JavaModelUtil.is50OrHigher(cu.getJavaProject()))
240
			return null;
241
242
		ASTNode selectedNode= problem.getCoveringNode(compilationUnit);
243
		if (selectedNode == null)
244
			return null;
245
		
246
		ASTNode declaringNode= getDeclaringNode(selectedNode);
247
				
248
		switch (problem.getProblemId()) {
249
		case IProblem.IllegalDefinitionToNonNullParameter:
250
//		case IllegalRedefinitionToNonNullParameter:
251
			// these affect another method
252
			break;
253
		case IProblem.IllegalReturnNullityRedefinition:
254
			if (declaringNode == null)
255
				declaringNode = selectedNode;
256
			break; // do propose changes even if we already have an annotation
257
		default:
258
			// if this method has annotations, don't change'em
259
			if (NullQuickFixes.hasExplicitNullAnnotation(cu, problem.getOffset()))
260
				return null;
261
		}
262
		
263
		SignatureAnnotationRewriteOperation result = null;
264
		String message = null;
265
		String annotationNameLabel = annotationToAdd; 
266
		int lastDot = annotationToAdd.lastIndexOf('.');
267
		if (lastDot != -1)
268
			annotationNameLabel = annotationToAdd.substring(lastDot+1);
269
		annotationNameLabel = BasicElementLabels.getJavaElementName(annotationNameLabel);
270
	
271
		if (selectedNode.getParent() instanceof MethodInvocation) {
272
			// DefiniteNullToNonNullParameter || PotentialNullToNonNullParameter
273
			MethodInvocation methodInvocation = (MethodInvocation)selectedNode.getParent();
274
			int paramIdx = methodInvocation.arguments().indexOf(selectedNode);
275
			IMethodBinding methodBinding = methodInvocation.resolveMethodBinding();
276
			compilationUnit = findCUForMethod(compilationUnit, cu, methodBinding);
277
			if (compilationUnit == null)
278
				return null;
279
			if (thisUnitOnly && !compilationUnit.getJavaElement().equals(cu))
280
				return null;
281
			ASTNode methodDecl = compilationUnit.findDeclaringNode(methodBinding.getKey());
282
			if (methodDecl == null)
283
				return null;
284
			message = Messages.format(NullFixMessages.QuickFixes_declare_method_parameter_nullness, annotationNameLabel);
285
			result = new ParameterAnnotationRewriteOperation(compilationUnit,
286
														     (MethodDeclaration) methodDecl,
287
														     annotationToAdd,
288
														     annotationToRemove,
289
														     paramIdx,
290
														     allowRemove,
291
														     message);
292
		} else if (declaringNode instanceof MethodDeclaration) {
293
			
294
			// complaint is in signature of this method
295
			
296
			MethodDeclaration declaration= (MethodDeclaration) declaringNode;
297
			
298
			switch (problem.getProblemId()) {
299
				case IProblem.ParameterLackingNonNullAnnotation:
300
				case IProblem.ParameterLackingNullableAnnotation:
301
				case IProblem.IllegalDefinitionToNonNullParameter:
302
				case IProblem.IllegalRedefinitionToNonNullParameter:
303
				case IProblem.NonNullLocalVariableComparisonYieldsFalse:
304
				case IProblem.RedundantNullCheckOnNonNullLocalVariable:
305
					// statements suggest changing parameters:
306
					if (declaration.getNodeType() == ASTNode.METHOD_DECLARATION) {
307
						String paramName = findAffectedParameterName(selectedNode);
308
						if (paramName != null) {
309
							message = Messages.format(NullFixMessages.QuickFixes_declare_method_parameter_nullness, annotationNameLabel);
310
							result = new ParameterAnnotationRewriteOperation(compilationUnit,
311
																		   declaration,
312
																		   annotationToAdd,
313
																		   annotationToRemove,
314
																		   paramName,
315
																		   allowRemove,
316
																		   message);
317
						}
318
					}
319
					break;
320
				case IProblem.IllegalReturnNullityRedefinition:
321
				case IProblem.RequiredNonNullButProvidedNull:
322
				case IProblem.RequiredNonNullButProvidedPotentialNull:
323
				case IProblem.RequiredNonNullButProvidedUnknown:
324
					message = Messages.format(NullFixMessages.QuickFixes_declare_method_return_nullness, annotationNameLabel);
325
					result = new ReturnAnnotationRewriteOperation(compilationUnit,
326
																    declaration,
327
																    annotationToAdd,
328
																    annotationToRemove,
329
																    allowRemove,
330
																    message);
331
					break;
332
			}
333
			
334
		}
335
		return result;
336
	}
337
	static SignatureAnnotationRewriteOperation createAddAnnotationToOverriddenOperation(CompilationUnit compilationUnit, 
338
																							   IProblemLocation problem, 
339
																							   String annotationToAdd, 
340
																							   String annotationToRemove, 
341
																							   Set<String> handledPositions,
342
																							   boolean thisUnitOnly, 
343
																							   boolean allowRemove) 
344
	{
345
		ICompilationUnit cu = (ICompilationUnit) compilationUnit.getJavaElement();
346
		if (!JavaModelUtil.is50OrHigher(cu.getJavaProject()))
347
			return null;
348
349
		ASTNode selectedNode = problem.getCoveringNode(compilationUnit);
350
		if (selectedNode == null)
351
			return null;
352
353
		ASTNode declaringNode = getDeclaringNode(selectedNode);
354
355
		switch (problem.getProblemId()) {
356
		case IProblem.IllegalDefinitionToNonNullParameter:
357
		case IProblem.IllegalRedefinitionToNonNullParameter:
358
			break;
359
		case IProblem.IllegalReturnNullityRedefinition:
360
			if (declaringNode == null)
361
				declaringNode = selectedNode;
362
			break;
363
		default:
364
			return null;
365
		}
366
367
		String annotationNameLabel = annotationToAdd;
368
		int lastDot = annotationToAdd.lastIndexOf('.');
369
		if (lastDot != -1)
370
			annotationNameLabel = annotationToAdd.substring(lastDot + 1);
371
		annotationNameLabel = BasicElementLabels.getJavaElementName(annotationNameLabel);
372
373
		if (declaringNode instanceof MethodDeclaration) {
374
375
			// complaint is in signature of this method
376
377
			MethodDeclaration declaration = (MethodDeclaration) declaringNode;
378
379
			switch (problem.getProblemId()) {
380
			case IProblem.IllegalDefinitionToNonNullParameter:
381
			case IProblem.IllegalRedefinitionToNonNullParameter:
382
				return createChangeOverriddenParameterOperation(compilationUnit, cu, declaration, selectedNode,
383
						allowRemove, annotationToAdd, annotationToRemove, annotationNameLabel);
384
			case IProblem.IllegalReturnNullityRedefinition:
385
				if (hasNullAnnotation(declaration)) { // don't adjust super if local has no explicit annotation (?)
386
					return createChangeOverriddenReturnOperation(compilationUnit, cu, declaration, allowRemove,
387
							annotationToAdd, annotationToRemove, annotationNameLabel);
388
				}
389
			}
390
391
		}
392
		return null;
393
	}
394
395
	static SignatureAnnotationRewriteOperation createChangeOverriddenParameterOperation(CompilationUnit compilationUnit,
396
																						ICompilationUnit cu,
397
																						MethodDeclaration declaration,
398
																						ASTNode selectedNode,
399
																						boolean allowRemove,
400
																						String annotationToAdd,
401
																						String annotationToRemove,
402
																						String annotationNameLabel)
403
	{
404
		SignatureAnnotationRewriteOperation result;
405
		String message;
406
		IMethodBinding methodDeclBinding= declaration.resolveBinding();
407
		if (methodDeclBinding == null)
408
			return null;
409
		
410
		IMethodBinding overridden= Bindings.findOverriddenMethod(methodDeclBinding, false);
411
		if (overridden == null)
412
			return null;
413
		compilationUnit = findCUForMethod(compilationUnit, cu, overridden);
414
		if (compilationUnit == null)
415
			return null;
416
		ASTNode methodDecl = compilationUnit.findDeclaringNode(overridden.getKey());
417
		if (methodDecl == null)
418
			return null;
419
		declaration = (MethodDeclaration) methodDecl;
420
		message = Messages.format(NullFixMessages.QuickFixes_declare_overridden_parameter_as_nonnull, 
421
				                  new String[] {
422
									annotationNameLabel,
423
									BasicElementLabels.getJavaElementName(overridden.getDeclaringClass().getName())
424
								  });
425
		String paramName = findAffectedParameterName(selectedNode);
426
		result = new ParameterAnnotationRewriteOperation(compilationUnit,
427
													     declaration,
428
													     annotationToAdd,
429
													     annotationToRemove,
430
													     paramName,
431
													     allowRemove,
432
													     message);
433
		return result;
434
	}
435
	
436
	static String findAffectedParameterName(ASTNode selectedNode) {
437
		VariableDeclaration argDecl = selectedNode instanceof VariableDeclaration
438
				? (VariableDeclaration) selectedNode
439
				: (VariableDeclaration) ASTNodes.getParent(selectedNode, VariableDeclaration.class);
440
		if (argDecl != null)
441
			return argDecl.getName().getIdentifier();
442
		if (selectedNode.getNodeType() == ASTNode.SIMPLE_NAME) {
443
			IBinding binding = ((SimpleName)selectedNode).resolveBinding();
444
			if (binding.getKind() == IBinding.VARIABLE && ((IVariableBinding)binding).isParameter())
445
				return ((SimpleName)selectedNode).getIdentifier();
446
		}
447
		return null;
448
	}
449
	
450
	static boolean hasNullAnnotation(MethodDeclaration decl) {
451
		List<IExtendedModifier> modifiers = decl.modifiers();
452
		String nonnull = NullQuickFixes.getNonNullAnnotationName(decl.resolveBinding().getJavaElement(), false);
453
		String nullable = NullQuickFixes.getNullableAnnotationName(decl.resolveBinding().getJavaElement(), false);
454
		for (Object mod : modifiers) {
455
			if (mod instanceof Annotation) {
456
				Name annotationName = ((Annotation) mod).getTypeName();
457
				String fullyQualifiedName = annotationName.getFullyQualifiedName();
458
				if (annotationName.isSimpleName() 
459
						? nonnull.endsWith(fullyQualifiedName) 
460
						: fullyQualifiedName.equals(nonnull))
461
					return true;
462
				if (annotationName.isSimpleName() 
463
						? nullable.endsWith(fullyQualifiedName) 
464
						: fullyQualifiedName.equals(nullable))
465
					return true;
466
			}
467
		}
468
		return false;
469
	}
470
471
	static SignatureAnnotationRewriteOperation createChangeOverriddenReturnOperation(CompilationUnit compilationUnit,
472
																					 ICompilationUnit cu,
473
																					 MethodDeclaration declaration,
474
																					 boolean allowRemove,
475
																					 String annotationToAdd,
476
																					 String annotationToRemove,
477
																					 String annotationNameLabel) 
478
	{
479
		SignatureAnnotationRewriteOperation result;
480
		String message;
481
		IMethodBinding methodDeclBinding= declaration.resolveBinding();
482
		if (methodDeclBinding == null)
483
			return null;
484
		
485
		IMethodBinding overridden= Bindings.findOverriddenMethod(methodDeclBinding, false);
486
		if (overridden == null)
487
			return null;
488
		compilationUnit = findCUForMethod(compilationUnit, cu, overridden);
489
		if (compilationUnit == null)
490
			return null;
491
		ASTNode methodDecl = compilationUnit.findDeclaringNode(overridden.getKey());
492
		if (methodDecl == null)
493
			return null;
494
		declaration = (MethodDeclaration) methodDecl;
495
// TODO(SH): decide whether we want to propose overwriting existing annotations in super
496
//		if (hasNullAnnotation(declaration)) // if overridden has explicit declaration don't propose to change it
497
//			return null;
498
		message = Messages.format(NullFixMessages.QuickFixes_declare_overridden_return_as_nullable, 
499
				                  new String[] {
500
									annotationNameLabel,
501
									BasicElementLabels.getJavaElementName(overridden.getDeclaringClass().getName())
502
								  });
503
		result = new ReturnAnnotationRewriteOperation(compilationUnit,
504
												      declaration,
505
												      annotationToAdd,
506
												      annotationToRemove,
507
												      allowRemove,
508
												      message);
509
		return result;
510
	}
511
512
	static CompilationUnit findCUForMethod(CompilationUnit compilationUnit, ICompilationUnit cu, IMethodBinding methodBinding) 
513
	{
514
		ASTNode methodDecl= compilationUnit.findDeclaringNode(methodBinding.getMethodDeclaration());
515
		if (methodDecl == null) {
516
			// is methodDecl defined in another CU?
517
			ITypeBinding declaringTypeDecl= methodBinding.getDeclaringClass().getTypeDeclaration();
518
			if (declaringTypeDecl.isFromSource()) {
519
				ICompilationUnit targetCU = null;
520
				try {
521
					targetCU = ASTResolving.findCompilationUnitForBinding(cu, compilationUnit, declaringTypeDecl);
522
				} catch (JavaModelException e) { /* can't do better */ }
523
				if (targetCU != null) {
524
					return ASTResolving.createQuickFixAST(targetCU, null);
525
				}
526
			}
527
			return null;
528
		}
529
		return compilationUnit;
530
	}
531
532
	/** The relevant declaring node of a return statement is the enclosing method. */
533
	static ASTNode getDeclaringNode(ASTNode selectedNode) {
534
		return ASTNodes.getParent(selectedNode, ASTNode.METHOD_DECLARATION);
535
	}
536
}

Return to bug 337977