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