b6960f0a5e8e7c985b25e98888dfa5567689e23b
[idea/community.git] / java / java-analysis-impl / src / com / intellij / codeInspection / nullable / NullableStuffInspectionBase.java
1 /*
2  * Copyright 2000-2016 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.intellij.codeInspection.nullable;
17
18 import com.intellij.codeInsight.AnnotationUtil;
19 import com.intellij.codeInsight.NullableNotNullManager;
20 import com.intellij.codeInsight.daemon.GroupNames;
21 import com.intellij.codeInsight.daemon.impl.analysis.HighlightControlFlowUtil;
22 import com.intellij.codeInsight.intention.AddAnnotationPsiFix;
23 import com.intellij.codeInsight.intention.impl.AddNotNullAnnotationFix;
24 import com.intellij.codeInspection.*;
25 import com.intellij.codeInspection.dataFlow.DfaPsiUtil;
26 import com.intellij.openapi.diagnostic.Logger;
27 import com.intellij.openapi.project.Project;
28 import com.intellij.openapi.util.WriteExternalException;
29 import com.intellij.openapi.util.text.StringUtil;
30 import com.intellij.psi.*;
31 import com.intellij.psi.codeStyle.JavaCodeStyleManager;
32 import com.intellij.psi.codeStyle.VariableKind;
33 import com.intellij.psi.impl.search.JavaNullMethodArgumentUtil;
34 import com.intellij.psi.search.GlobalSearchScope;
35 import com.intellij.psi.search.searches.OverridingMethodsSearch;
36 import com.intellij.psi.util.*;
37 import com.intellij.util.ArrayUtil;
38 import com.intellij.util.containers.ContainerUtil;
39 import org.jdom.Element;
40 import org.jetbrains.annotations.NotNull;
41
42 import javax.swing.*;
43 import java.util.ArrayList;
44 import java.util.List;
45 import java.util.Set;
46
47 public class NullableStuffInspectionBase extends BaseJavaBatchLocalInspectionTool {
48   // deprecated fields remain to minimize changes to users inspection profiles (which are often located in version control).
49   @Deprecated @SuppressWarnings({"WeakerAccess"}) public boolean REPORT_NULLABLE_METHOD_OVERRIDES_NOTNULL = true;
50   @SuppressWarnings({"WeakerAccess"}) public boolean REPORT_NOT_ANNOTATED_METHOD_OVERRIDES_NOTNULL = true;
51   @SuppressWarnings({"WeakerAccess"}) public boolean REPORT_NOTNULL_PARAMETER_OVERRIDES_NULLABLE = true;
52   @Deprecated @SuppressWarnings({"WeakerAccess"}) public boolean REPORT_NOT_ANNOTATED_PARAMETER_OVERRIDES_NOTNULL = true;
53   @SuppressWarnings({"WeakerAccess"}) public boolean REPORT_NOT_ANNOTATED_GETTER = true;
54   @SuppressWarnings({"WeakerAccess"}) public boolean IGNORE_EXTERNAL_SUPER_NOTNULL;
55   @SuppressWarnings({"WeakerAccess"}) public boolean REQUIRE_NOTNULL_FIELDS_INITIALIZED = true;
56   @SuppressWarnings({"WeakerAccess"}) public boolean REPORT_NOTNULL_PARAMETERS_OVERRIDES_NOT_ANNOTATED;
57   @Deprecated @SuppressWarnings({"WeakerAccess"}) public boolean REPORT_NOT_ANNOTATED_SETTER_PARAMETER = true;
58   @Deprecated @SuppressWarnings({"WeakerAccess"}) public boolean REPORT_ANNOTATION_NOT_PROPAGATED_TO_OVERRIDERS = true; // remains for test
59   @SuppressWarnings({"WeakerAccess"}) public boolean REPORT_NULLS_PASSED_TO_NON_ANNOTATED_METHOD = true;
60   public boolean REPORT_NULLS_PASSED_TO_NOT_NULL_PARAMETER = true;
61
62   private static final Logger LOG = Logger.getInstance("#com.intellij.codeInspection.nullable.NullableStuffInspectionBase");
63
64   @Override
65   public void writeSettings(@NotNull Element node) throws WriteExternalException {
66     super.writeSettings(node);
67     for (Element child : new ArrayList<>(node.getChildren())) {
68       String name = child.getAttributeValue("name");
69       String value = child.getAttributeValue("value");
70       if ("IGNORE_EXTERNAL_SUPER_NOTNULL".equals(name) && "false".equals(value) ||
71           "REPORT_NOTNULL_PARAMETERS_OVERRIDES_NOT_ANNOTATED".equals(name) && "false".equals(value) ||
72           "REQUIRE_NOTNULL_FIELDS_INITIALIZED".equals(name) && "true".equals(value) ||
73           "REPORT_NULLS_PASSED_TO_NOT_NULL_PARAMETER".equals(name) && "true".equals(value)) {
74         node.removeContent(child);
75       }
76     }
77   }
78
79   @Override
80   @NotNull
81   public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) {
82     final PsiFile file = holder.getFile();
83     if (!PsiUtil.isLanguageLevel5OrHigher(file) || nullabilityAnnotationsNotAvailable(file)) {
84       return new PsiElementVisitor() { };
85     }
86     return new JavaElementVisitor() {
87       @Override
88       public void visitMethod(PsiMethod method) {
89         checkNullableStuffForMethod(method, holder, isOnTheFly);
90       }
91
92       @Override
93       public void visitField(PsiField field) {
94         final PsiType type = field.getType();
95         final Annotated annotated = check(field, holder, type);
96         if (TypeConversionUtil.isPrimitiveAndNotNull(type)) {
97           return;
98         }
99         Project project = holder.getProject();
100         final NullableNotNullManager manager = NullableNotNullManager.getInstance(project);
101         if (annotated.isDeclaredNotNull ^ annotated.isDeclaredNullable) {
102           final String anno = annotated.isDeclaredNotNull ? manager.getDefaultNotNull() : manager.getDefaultNullable();
103           final List<String> annoToRemove = annotated.isDeclaredNotNull ? manager.getNullables() : manager.getNotNulls();
104
105           if (!checkNonStandardAnnotations(field, annotated, manager, anno, holder)) return;
106
107           checkAccessors(field, annotated, project, manager, anno, annoToRemove, holder);
108
109           checkConstructorParameters(field, annotated, manager, anno, annoToRemove, holder);
110         }
111
112         if (REQUIRE_NOTNULL_FIELDS_INITIALIZED && !annotated.isDeclaredNullable) {
113           checkNotNullFieldsInitialized(field, manager, holder);
114         }
115       }
116
117       @Override
118       public void visitParameter(PsiParameter parameter) {
119         check(parameter, holder, parameter.getType());
120       }
121
122       @Override
123       public void visitAnnotation(PsiAnnotation annotation) {
124         if (!AnnotationUtil.NOT_NULL.equals(annotation.getQualifiedName())) return;
125
126         PsiAnnotationMemberValue value = annotation.findDeclaredAttributeValue("exception");
127         if (value instanceof PsiClassObjectAccessExpression) {
128           PsiClass psiClass = PsiUtil.resolveClassInClassTypeOnly(((PsiClassObjectAccessExpression)value).getOperand().getType());
129           if (psiClass != null && !hasStringConstructor(psiClass)) {
130             //noinspection DialogTitleCapitalization
131             holder.registerProblem(value,
132                                    "Custom exception class should have a constructor with a single message parameter of String type",
133                                    ProblemHighlightType.GENERIC_ERROR_OR_WARNING);
134
135           }
136         }
137       }
138
139       private boolean hasStringConstructor(PsiClass aClass) {
140         for (PsiMethod method : aClass.getConstructors()) {
141           PsiParameterList list = method.getParameterList();
142           if (list.getParametersCount() == 1 &&
143               list.getParameters()[0].getType().equalsToText(CommonClassNames.JAVA_LANG_STRING)) {
144             return true;
145           }
146         }
147         return false;
148       }
149     };
150   }
151
152   protected LocalQuickFix createNavigateToNullParameterUsagesFix(PsiParameter parameter) {
153     return null;
154   }
155
156   private static boolean nullabilityAnnotationsNotAvailable(final PsiFile file) {
157     final Project project = file.getProject();
158     final GlobalSearchScope scope = GlobalSearchScope.allScope(project);
159     final JavaPsiFacade facade = JavaPsiFacade.getInstance(project);
160     return ContainerUtil.find(NullableNotNullManager.getInstance(project).getNullables(), s -> facade.findClass(s, scope) != null) == null;
161   }
162
163   private static boolean checkNonStandardAnnotations(PsiField field,
164                                                      Annotated annotated,
165                                                      NullableNotNullManager manager, String anno, @NotNull ProblemsHolder holder) {
166     if (!AnnotationUtil.isAnnotatingApplicable(field, anno)) {
167       final PsiAnnotation notNull = AnnotationUtil.findAnnotation(field, manager.getNotNulls());
168       final PsiAnnotation nullable = AnnotationUtil.findAnnotation(field, manager.getNullables());
169       final PsiAnnotation annotation;
170       String message = "Not \'";
171       if (annotated.isDeclaredNullable) {
172         message += nullable.getQualifiedName();
173         annotation = nullable;
174       } else {
175         message += notNull.getQualifiedName();
176         annotation = notNull;
177       }
178       message += "\' but \'" + anno + "\' would be used for code generation.";
179       final PsiJavaCodeReferenceElement annotationNameReferenceElement = annotation.getNameReferenceElement();
180       holder.registerProblem(annotationNameReferenceElement != null && annotationNameReferenceElement.isPhysical() ? annotationNameReferenceElement : field.getNameIdentifier(),
181                              message,
182                              ProblemHighlightType.WEAK_WARNING,
183                              new ChangeNullableDefaultsFix(notNull, nullable, manager));
184       return false;
185     }
186     return true;
187   }
188
189   private void checkAccessors(PsiField field,
190                               Annotated annotated,
191                               Project project,
192                               NullableNotNullManager manager, final String anno, final List<String> annoToRemove, @NotNull ProblemsHolder holder) {
193     String propName = JavaCodeStyleManager.getInstance(project).variableNameToPropertyName(field.getName(), VariableKind.FIELD);
194     final boolean isStatic = field.hasModifierProperty(PsiModifier.STATIC);
195     final PsiMethod getter = PropertyUtil.findPropertyGetter(field.getContainingClass(), propName, isStatic, false);
196     final PsiIdentifier nameIdentifier = getter == null ? null : getter.getNameIdentifier();
197     if (nameIdentifier != null && nameIdentifier.isPhysical()) {
198       if (PropertyUtil.isSimpleGetter(getter)) {
199         AnnotateMethodFix getterAnnoFix = new AnnotateMethodFix(anno, ArrayUtil.toStringArray(annoToRemove)) {
200           @Override
201           public int shouldAnnotateBaseMethod(PsiMethod method, PsiMethod superMethod, Project project) {
202             return 1;
203           }
204         };
205         if (REPORT_NOT_ANNOTATED_GETTER) {
206           if (!manager.hasNullability(getter) && !TypeConversionUtil.isPrimitiveAndNotNull(getter.getReturnType())) {
207             holder.registerProblem(nameIdentifier, InspectionsBundle
208                                      .message("inspection.nullable.problems.annotated.field.getter.not.annotated", getPresentableAnnoName(field)),
209                                    ProblemHighlightType.GENERIC_ERROR_OR_WARNING, getterAnnoFix);
210           }
211         }
212         if (annotated.isDeclaredNotNull && isNullableNotInferred(getter, false) ||
213             annotated.isDeclaredNullable && isNotNullNotInferred(getter, false, false)) {
214           holder.registerProblem(nameIdentifier, InspectionsBundle.message(
215                                    "inspection.nullable.problems.annotated.field.getter.conflict", getPresentableAnnoName(field), getPresentableAnnoName(getter)),
216                                  ProblemHighlightType.GENERIC_ERROR_OR_WARNING, getterAnnoFix);
217         }
218       }
219     }
220
221     final PsiClass containingClass = field.getContainingClass();
222     final PsiMethod setter = PropertyUtil.findPropertySetter(containingClass, propName, isStatic, false);
223     if (setter != null && setter.isPhysical()) {
224       final PsiParameter[] parameters = setter.getParameterList().getParameters();
225       assert parameters.length == 1 : setter.getText();
226       final PsiParameter parameter = parameters[0];
227       LOG.assertTrue(parameter != null, setter.getText());
228       AddAnnotationPsiFix addAnnoFix = createAddAnnotationFix(anno, annoToRemove, parameter);
229       if (REPORT_NOT_ANNOTATED_GETTER && !manager.hasNullability(parameter) && !TypeConversionUtil.isPrimitiveAndNotNull(parameter.getType())) {
230         final PsiIdentifier nameIdentifier1 = parameter.getNameIdentifier();
231         assertValidElement(setter, parameter, nameIdentifier1);
232         holder.registerProblem(nameIdentifier1,
233                                InspectionsBundle.message("inspection.nullable.problems.annotated.field.setter.parameter.not.annotated",
234                                                          getPresentableAnnoName(field)),
235                                ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
236                                addAnnoFix);
237       }
238       if (PropertyUtil.isSimpleSetter(setter)) {
239         if (annotated.isDeclaredNotNull && isNullableNotInferred(parameter, false)) {
240           final PsiIdentifier nameIdentifier1 = parameter.getNameIdentifier();
241           assertValidElement(setter, parameter, nameIdentifier1);
242           holder.registerProblem(nameIdentifier1, InspectionsBundle.message(
243                                    "inspection.nullable.problems.annotated.field.setter.parameter.conflict",
244                                    getPresentableAnnoName(field), getPresentableAnnoName(parameter)),
245                                  ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
246                                  addAnnoFix);
247         }
248       }
249     }
250   }
251
252   @NotNull
253   private static AddAnnotationPsiFix createAddAnnotationFix(String anno, List<String> annoToRemove, PsiParameter parameter) {
254     return new AddAnnotationPsiFix(anno, parameter, PsiNameValuePair.EMPTY_ARRAY, ArrayUtil.toStringArray(annoToRemove));
255   }
256
257   private static void assertValidElement(PsiMethod setter, PsiParameter parameter, PsiIdentifier nameIdentifier1) {
258     LOG.assertTrue(nameIdentifier1 != null && nameIdentifier1.isPhysical(), setter.getText());
259     LOG.assertTrue(parameter.isPhysical(), setter.getText());
260   }
261
262   private static void checkNotNullFieldsInitialized(PsiField field, NullableNotNullManager manager, @NotNull ProblemsHolder holder) {
263     PsiAnnotation annotation = manager.getNotNullAnnotation(field, false);
264     if (annotation == null || HighlightControlFlowUtil.isFieldInitializedAfterObjectConstruction(field)) return;
265
266     boolean byDefault = manager.isContainerAnnotation(annotation);
267     PsiJavaCodeReferenceElement name = annotation.getNameReferenceElement();
268     holder.registerProblem(annotation.isPhysical() && !byDefault ? annotation : field.getNameIdentifier(),
269                            (byDefault && name != null ? "@" + name.getReferenceName() : "Not-null") + " fields must be initialized");
270   }
271
272   private void checkConstructorParameters(PsiField field,
273                                           Annotated annotated,
274                                           NullableNotNullManager manager,
275                                           String anno, List<String> annoToRemove, @NotNull ProblemsHolder holder) {
276     List<PsiExpression> initializers = DfaPsiUtil.findAllConstructorInitializers(field);
277     if (initializers.isEmpty()) return;
278     
279     List<PsiParameter> notNullParams = ContainerUtil.newArrayList();
280
281     boolean isFinal = field.hasModifierProperty(PsiModifier.FINAL);
282
283     for (PsiExpression rhs : initializers) {
284       if (rhs instanceof PsiReferenceExpression) {
285         PsiElement target = ((PsiReferenceExpression)rhs).resolve();
286         if (target instanceof PsiParameter && target.isPhysical()) {
287           PsiParameter parameter = (PsiParameter)target;
288           if (REPORT_NOT_ANNOTATED_GETTER && !manager.hasNullability(parameter) && !TypeConversionUtil.isPrimitiveAndNotNull(parameter.getType())) {
289             final PsiIdentifier nameIdentifier = parameter.getNameIdentifier();
290             if (nameIdentifier != null && nameIdentifier.isPhysical()) {
291               holder.registerProblem(
292                 nameIdentifier,
293                 InspectionsBundle.message("inspection.nullable.problems.annotated.field.constructor.parameter.not.annotated", getPresentableAnnoName(field)),
294                 ProblemHighlightType.GENERIC_ERROR_OR_WARNING, createAddAnnotationFix(anno, annoToRemove, parameter));
295               continue;
296             }
297           }
298
299           if (isFinal && annotated.isDeclaredNullable && isNotNullNotInferred(parameter, false, false)) {
300             notNullParams.add(parameter);
301           }
302
303         }
304       }
305     }
306
307     if (notNullParams.size() != initializers.size()) {
308       // it's not the case that the field is final and @Nullable and always initialized via @NotNull parameters
309       // so there might be other initializers that could justify it being nullable
310       // so don't highlight field and constructor parameter annotation inconsistency
311       return;
312     }
313
314     PsiIdentifier nameIdentifier = field.getNameIdentifier();
315     if (nameIdentifier.isPhysical()) {
316       holder.registerProblem(nameIdentifier, "@" + getPresentableAnnoName(field) + " field is always initialized not-null", 
317                              ProblemHighlightType.GENERIC_ERROR_OR_WARNING, new AddNotNullAnnotationFix(field));
318     }
319   }
320
321   @NotNull
322   private static String getPresentableAnnoName(@NotNull PsiModifierListOwner owner) {
323     NullableNotNullManager manager = NullableNotNullManager.getInstance(owner.getProject());
324     Set<String> names = ContainerUtil.newHashSet(manager.getNullables());
325     names.addAll(manager.getNotNulls());
326
327     PsiAnnotation annotation = AnnotationUtil.findAnnotationInHierarchy(owner, names);
328     if (annotation != null) return getPresentableAnnoName(annotation);
329     
330     String anno = manager.getNotNull(owner);
331     return StringUtil.getShortName(anno != null ? anno : StringUtil.notNullize(manager.getNullable(owner), "???"));
332   }
333
334   public static String getPresentableAnnoName(@NotNull PsiAnnotation annotation) {
335     return StringUtil.getShortName(StringUtil.notNullize(annotation.getQualifiedName(), "???"));
336   }
337
338   private static class Annotated {
339     private final boolean isDeclaredNotNull;
340     private final boolean isDeclaredNullable;
341
342     private Annotated(final boolean isDeclaredNotNull, final boolean isDeclaredNullable) {
343       this.isDeclaredNotNull = isDeclaredNotNull;
344       this.isDeclaredNullable = isDeclaredNullable;
345     }
346   }
347   private static Annotated check(final PsiModifierListOwner parameter, final ProblemsHolder holder, PsiType type) {
348     final NullableNotNullManager manager = NullableNotNullManager.getInstance(holder.getProject());
349     PsiAnnotation isDeclaredNotNull = AnnotationUtil.findAnnotation(parameter, manager.getNotNulls());
350     PsiAnnotation isDeclaredNullable = AnnotationUtil.findAnnotation(parameter, manager.getNullables());
351     if (isDeclaredNullable != null && isDeclaredNotNull != null) {
352       reportNullableNotNullConflict(holder, parameter, isDeclaredNullable, isDeclaredNotNull);
353     }
354     if ((isDeclaredNotNull != null || isDeclaredNullable != null) && type != null && TypeConversionUtil.isPrimitive(type.getCanonicalText())) {
355       PsiAnnotation annotation = isDeclaredNotNull == null ? isDeclaredNullable : isDeclaredNotNull;
356       reportPrimitiveType(holder, annotation, annotation, parameter);
357     }
358     return new Annotated(isDeclaredNotNull != null,isDeclaredNullable != null);
359   }
360
361   private static void reportPrimitiveType(final ProblemsHolder holder, final PsiElement psiElement, final PsiAnnotation annotation,
362                                           final PsiModifierListOwner listOwner) {
363     holder.registerProblem(psiElement.isPhysical() ? psiElement : listOwner.getNavigationElement(),
364                            InspectionsBundle.message("inspection.nullable.problems.primitive.type.annotation"),
365                            ProblemHighlightType.GENERIC_ERROR_OR_WARNING, new RemoveAnnotationQuickFix(annotation, listOwner));
366   }
367
368   @Override
369   @NotNull
370   public String getDisplayName() {
371     return InspectionsBundle.message("inspection.nullable.problems.display.name");
372   }
373
374   @Override
375   @NotNull
376   public String getGroupDisplayName() {
377     return GroupNames.BUGS_GROUP_NAME;
378   }
379
380   @Override
381   @NotNull
382   public String getShortName() {
383     return "NullableProblems";
384   }
385
386   private void checkNullableStuffForMethod(PsiMethod method, final ProblemsHolder holder, boolean isOnFly) {
387     Annotated annotated = check(method, holder, method.getReturnType());
388
389     List<PsiMethod> superMethods = ContainerUtil.map(
390       method.findSuperMethodSignaturesIncludingStatic(true), signature -> signature.getMethod());
391
392     final NullableNotNullManager nullableManager = NullableNotNullManager.getInstance(holder.getProject());
393
394     checkSupers(method, holder, annotated, superMethods, nullableManager);
395     checkParameters(method, holder, superMethods, nullableManager, isOnFly);
396     checkOverriders(method, holder, annotated, nullableManager);
397   }
398
399   private void checkSupers(PsiMethod method,
400                            ProblemsHolder holder,
401                            Annotated annotated,
402                            List<PsiMethod> superMethods, NullableNotNullManager nullableManager) {
403     if (REPORT_NOTNULL_PARAMETER_OVERRIDES_NULLABLE) {
404       for (PsiMethod superMethod : superMethods) {
405         if (annotated.isDeclaredNullable && isNotNullNotInferred(superMethod, true, false)) {
406           final PsiAnnotation annotation = AnnotationUtil.findAnnotation(method, nullableManager.getNullables(), true);
407           holder.registerProblem(annotation != null ? annotation : method.getNameIdentifier(),
408                                  InspectionsBundle.message("inspection.nullable.problems.Nullable.method.overrides.NotNull",
409                                                            getPresentableAnnoName(method), getPresentableAnnoName(superMethod)),
410                                  ProblemHighlightType.GENERIC_ERROR_OR_WARNING);
411           break;
412         }
413       }
414     }
415
416     if (REPORT_NOT_ANNOTATED_METHOD_OVERRIDES_NOTNULL) {
417       for (PsiMethod superMethod : superMethods) {
418         if (!nullableManager.hasNullability(method) && isNotNullNotInferred(superMethod, true, IGNORE_EXTERNAL_SUPER_NOTNULL)) {
419           final String defaultNotNull = nullableManager.getDefaultNotNull();
420           final String[] annotationsToRemove = ArrayUtil.toStringArray(nullableManager.getNullables());
421           final LocalQuickFix fix = AnnotationUtil.isAnnotatingApplicable(method, defaultNotNull)
422                                     ? createAnnotateMethodFix(defaultNotNull, annotationsToRemove)
423                                     : createChangeDefaultNotNullFix(nullableManager, superMethod);
424           holder.registerProblem(method.getNameIdentifier(),
425                                  InspectionsBundle.message("inspection.nullable.problems.method.overrides.NotNull", getPresentableAnnoName(superMethod)),
426                                  ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
427                                  fix);
428           break;
429         }
430       }
431     }
432   }
433
434   private void checkParameters(PsiMethod method,
435                                ProblemsHolder holder,
436                                List<PsiMethod> superMethods,
437                                NullableNotNullManager nullableManager,
438                                boolean isOnFly) {
439     PsiParameter[] parameters = method.getParameterList().getParameters();
440     for (int i = 0; i < parameters.length; i++) {
441       PsiParameter parameter = parameters[i];
442
443       List<PsiParameter> superParameters = ContainerUtil.newArrayList();
444       for (PsiMethod superMethod : superMethods) {
445         PsiParameter[] _superParameters = superMethod.getParameterList().getParameters();
446         if (_superParameters.length == parameters.length) {
447           superParameters.add(_superParameters[i]);
448         }
449       }
450
451       if (REPORT_NOTNULL_PARAMETER_OVERRIDES_NULLABLE) {
452         for (PsiParameter superParameter : superParameters) {
453           if (isNotNullNotInferred(parameter, false, false) &&
454               isNullableNotInferred(superParameter, false)) {
455             final PsiAnnotation annotation = AnnotationUtil.findAnnotation(parameter, nullableManager.getNotNulls(), true);
456             holder.registerProblem(annotation != null ? annotation : parameter.getNameIdentifier(),
457                                    InspectionsBundle.message("inspection.nullable.problems.NotNull.parameter.overrides.Nullable",
458                                                              getPresentableAnnoName(parameter),
459                                                              getPresentableAnnoName(superParameter)),
460                                    ProblemHighlightType.GENERIC_ERROR_OR_WARNING);
461             break;
462           }
463         }
464       }
465       if (REPORT_NOT_ANNOTATED_METHOD_OVERRIDES_NOTNULL) {
466         for (PsiParameter superParameter : superParameters) {
467           if (!nullableManager.hasNullability(parameter) && isNotNullNotInferred(superParameter, false, IGNORE_EXTERNAL_SUPER_NOTNULL)) {
468             final LocalQuickFix fix = AnnotationUtil.isAnnotatingApplicable(parameter, nullableManager.getDefaultNotNull())
469                                       ? new AddNotNullAnnotationFix(parameter)
470                                       : createChangeDefaultNotNullFix(nullableManager, superParameter);
471             holder.registerProblem(parameter.getNameIdentifier(),
472                                    InspectionsBundle.message("inspection.nullable.problems.parameter.overrides.NotNull", getPresentableAnnoName(superParameter)),
473                                    ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
474                                    fix);
475             break;
476           }
477         }
478       }
479       if (REPORT_NOTNULL_PARAMETERS_OVERRIDES_NOT_ANNOTATED) {
480         for (PsiParameter superParameter : superParameters) {
481           if (!nullableManager.hasNullability(superParameter) && isNotNullNotInferred(parameter, false, false)) {
482             PsiAnnotation notNullAnnotation = nullableManager.getNotNullAnnotation(parameter, false);
483             assert notNullAnnotation != null;
484             boolean physical = PsiTreeUtil.isAncestor(parameter, notNullAnnotation, true);
485             final LocalQuickFix fix = physical ? new RemoveAnnotationQuickFix(notNullAnnotation, parameter) : null;
486             holder.registerProblem(physical ? notNullAnnotation : parameter.getNameIdentifier(),
487                                    InspectionsBundle.message("inspection.nullable.problems.NotNull.parameter.overrides.not.annotated", getPresentableAnnoName(parameter)),
488                                    ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
489                                    fix);
490             break;
491           }
492         }
493       }
494
495       checkNullLiteralArgumentOfNotNullParameterUsages(method, holder, nullableManager, isOnFly, i, parameter);
496     }
497   }
498
499   private void checkNullLiteralArgumentOfNotNullParameterUsages(PsiMethod method,
500                                                                 ProblemsHolder holder,
501                                                                 NullableNotNullManager nullableManager,
502                                                                 boolean isOnFly,
503                                                                 int parameterIdx,
504                                                                 PsiParameter parameter) {
505     if (REPORT_NULLS_PASSED_TO_NOT_NULL_PARAMETER && isOnFly && isNotNullNotInferred(parameter, false, false)) {
506       PsiAnnotation notNullAnnotation = nullableManager.getNotNullAnnotation(parameter, false);
507       if (JavaNullMethodArgumentUtil.hasNullArgument(method, parameterIdx)) {
508         boolean physical = PsiTreeUtil.isAncestor(parameter, notNullAnnotation, true);
509         holder.registerProblem(physical ? notNullAnnotation : parameter.getNameIdentifier(),
510                                InspectionsBundle.message("inspection.nullable.problems.NotNull.parameter.receives.null.literal", getPresentableAnnoName(parameter)),
511                                ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
512                                createNavigateToNullParameterUsagesFix(parameter));
513       }
514     }
515   }
516
517   private void checkOverriders(PsiMethod method,
518                                ProblemsHolder holder,
519                                Annotated annotated,
520                                NullableNotNullManager nullableManager) {
521     PsiParameter[] parameters = method.getParameterList().getParameters();
522     if (REPORT_ANNOTATION_NOT_PROPAGATED_TO_OVERRIDERS) {
523       boolean[] parameterAnnotated = new boolean[parameters.length];
524       boolean[] parameterQuickFixSuggested = new boolean[parameters.length];
525       boolean hasAnnotatedParameter = false;
526       for (int i = 0; i < parameters.length; i++) {
527         PsiParameter parameter = parameters[i];
528         parameterAnnotated[i] = isNotNullNotInferred(parameter, false, false);
529         hasAnnotatedParameter |= parameterAnnotated[i];
530       }
531       if (hasAnnotatedParameter || annotated.isDeclaredNotNull) {
532         PsiManager manager = method.getManager();
533         final String defaultNotNull = nullableManager.getDefaultNotNull();
534         final boolean superMethodApplicable = AnnotationUtil.isAnnotatingApplicable(method, defaultNotNull);
535         PsiMethod[] overridings =
536           OverridingMethodsSearch.search(method).toArray(PsiMethod.EMPTY_ARRAY);
537         boolean methodQuickFixSuggested = false;
538         for (PsiMethod overriding : overridings) {
539           if (!manager.isInProject(overriding)) continue;
540
541           final boolean applicable = AnnotationUtil.isAnnotatingApplicable(overriding, defaultNotNull);
542           if (!methodQuickFixSuggested
543               && annotated.isDeclaredNotNull
544               && !isNotNullNotInferred(overriding, false, false)
545               && (isNullableNotInferred(overriding, false) || !isNullableNotInferred(overriding, true))) {
546             method.getNameIdentifier(); //load tree
547             PsiAnnotation annotation = AnnotationUtil.findAnnotation(method, nullableManager.getNotNulls());
548             final String[] annotationsToRemove = ArrayUtil.toStringArray(nullableManager.getNullables());
549
550             final LocalQuickFix fix;
551             if (applicable) {
552               fix = new MyAnnotateMethodFix(defaultNotNull, annotationsToRemove);
553             }
554             else {
555               fix = superMethodApplicable ? null : createChangeDefaultNotNullFix(nullableManager, method);
556             }
557
558             PsiElement psiElement = annotation;
559             if (!annotation.isPhysical()) {
560               psiElement = method.getNameIdentifier();
561               if (psiElement == null) continue;
562             }
563             holder.registerProblem(psiElement, InspectionsBundle.message("nullable.stuff.problems.overridden.methods.are.not.annotated"),
564                                    ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
565                                    fix);
566             methodQuickFixSuggested = true;
567           }
568           if (hasAnnotatedParameter) {
569             PsiParameter[] psiParameters = overriding.getParameterList().getParameters();
570             for (int i = 0; i < psiParameters.length; i++) {
571               if (parameterQuickFixSuggested[i]) continue;
572               PsiParameter parameter = psiParameters[i];
573               if (parameterAnnotated[i] && !isNotNullNotInferred(parameter, false, false) && !isNullableNotInferred(parameter, false)) {
574                 parameters[i].getNameIdentifier(); //be sure that corresponding tree element available
575                 PsiAnnotation annotation = AnnotationUtil.findAnnotation(parameters[i], nullableManager.getNotNulls());
576                 PsiElement psiElement = annotation;
577                 if (annotation == null || !annotation.isPhysical()) {
578                   psiElement = parameters[i].getNameIdentifier();
579                   if (psiElement == null) continue;
580                 }
581                 holder.registerProblem(psiElement,
582                                        InspectionsBundle.message("nullable.stuff.problems.overridden.method.parameters.are.not.annotated"),
583                                        ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
584                                        !applicable
585                                                ? createChangeDefaultNotNullFix(nullableManager, parameters[i])
586                                                : new AnnotateOverriddenMethodParameterFix(defaultNotNull,
587                                                                                           nullableManager.getDefaultNullable()));
588                 parameterQuickFixSuggested[i] = true;
589               }
590             }
591           }
592         }
593       }
594     }
595   }
596
597   private static boolean isNotNullNotInferred(@NotNull PsiModifierListOwner owner, boolean checkBases, boolean skipExternal) {
598     Project project = owner.getProject();
599     NullableNotNullManager manager = NullableNotNullManager.getInstance(project);
600     if (!manager.isNotNull(owner, checkBases)) return false;
601
602     PsiAnnotation anno = manager.getNotNullAnnotation(owner, checkBases);
603     if (anno == null || AnnotationUtil.isInferredAnnotation(anno)) return false;
604     if (skipExternal && AnnotationUtil.isExternalAnnotation(anno)) return false;
605     return true;
606   }
607
608   public static boolean isNullableNotInferred(@NotNull PsiModifierListOwner owner, boolean checkBases) {
609     Project project = owner.getProject();
610     NullableNotNullManager manager = NullableNotNullManager.getInstance(project);
611     if (!manager.isNullable(owner, checkBases)) return false;
612
613     PsiAnnotation anno = manager.getNullableAnnotation(owner, checkBases);
614     return !(anno != null && AnnotationUtil.isInferredAnnotation(anno));
615   }
616
617   private static LocalQuickFix createChangeDefaultNotNullFix(NullableNotNullManager nullableManager, PsiModifierListOwner modifierListOwner) {
618     final PsiAnnotation annotation = AnnotationUtil.findAnnotation(modifierListOwner, nullableManager.getNotNulls());
619     if (annotation != null) {
620       final PsiJavaCodeReferenceElement referenceElement = annotation.getNameReferenceElement();
621       if (referenceElement != null && referenceElement.resolve() != null) {
622         return new ChangeNullableDefaultsFix(annotation.getQualifiedName(), null, nullableManager);
623       }
624     }
625     return null;
626   }
627
628   protected AnnotateMethodFix createAnnotateMethodFix(final String defaultNotNull, final String[] annotationsToRemove) {
629     return new AnnotateMethodFix(defaultNotNull, annotationsToRemove);
630   }
631
632   private static void reportNullableNotNullConflict(final ProblemsHolder holder, final PsiModifierListOwner listOwner, final PsiAnnotation declaredNullable,
633                                                     final PsiAnnotation declaredNotNull) {
634     final String bothNullableNotNullMessage = InspectionsBundle.message("inspection.nullable.problems.Nullable.NotNull.conflict", 
635                                                                         getPresentableAnnoName(declaredNullable),
636                                                                         getPresentableAnnoName(declaredNotNull));
637     holder.registerProblem(declaredNotNull.isPhysical() ? declaredNotNull : listOwner.getNavigationElement(),
638                            bothNullableNotNullMessage,
639                            ProblemHighlightType.GENERIC_ERROR_OR_WARNING, new RemoveAnnotationQuickFix(declaredNotNull, listOwner));
640     holder.registerProblem(declaredNullable.isPhysical() ? declaredNullable : listOwner.getNavigationElement(),
641                            bothNullableNotNullMessage,
642                            ProblemHighlightType.GENERIC_ERROR_OR_WARNING, new RemoveAnnotationQuickFix(declaredNullable, listOwner));
643   }
644
645   @Override
646   public JComponent createOptionsPanel() {
647     throw new RuntimeException("No UI in headless mode");
648   }
649
650   private static class MyAnnotateMethodFix extends AnnotateMethodFix {
651     public MyAnnotateMethodFix(String defaultNotNull, String[] annotationsToRemove) {
652       super(defaultNotNull, annotationsToRemove);
653     }
654
655     @Override
656     protected boolean annotateOverriddenMethods() {
657       return true;
658     }
659
660     @Override
661     public int shouldAnnotateBaseMethod(PsiMethod method, PsiMethod superMethod, Project project) {
662       return 1;
663     }
664
665     @Override
666     @NotNull
667     public String getName() {
668       return InspectionsBundle.message("annotate.overridden.methods.as.notnull", ClassUtil.extractClassName(myAnnotation));
669     }
670   }
671 }