diff --git a/java/debugger/impl/src/com/intellij/debugger/settings/CaptureConfigurable.java b/java/debugger/impl/src/com/intellij/debugger/settings/CaptureConfigurable.java index 62ab02a21dba..f3c5500692a0 100644 --- a/java/debugger/impl/src/com/intellij/debugger/settings/CaptureConfigurable.java +++ b/java/debugger/impl/src/com/intellij/debugger/settings/CaptureConfigurable.java @@ -574,13 +574,13 @@ public class CaptureConfigurable implements SearchableConfigurable, NoScroll { "Async Schedule", "", getAsyncAnnotations(mySettings, true), - new String[]{getAnnotationName(true)}, + Collections.singletonList(getAnnotationName(true)), Collections.emptySet(), false, false); myAsyncExecutePanel = new AnnotationsPanel(project, "Async Execute", "", getAsyncAnnotations(mySettings, false), - new String[]{getAnnotationName(false)}, + Collections.singletonList(getAnnotationName(false)), Collections.emptySet(), false, false); init(); setTitle("Async Annotations Configuration"); diff --git a/java/java-impl/src/com/intellij/codeInsight/NullableNotNullManagerImpl.java b/java/java-impl/src/com/intellij/codeInsight/NullableNotNullManagerImpl.java index 1d956a672cd8..31ec4dec855c 100644 --- a/java/java-impl/src/com/intellij/codeInsight/NullableNotNullManagerImpl.java +++ b/java/java-impl/src/com/intellij/codeInsight/NullableNotNullManagerImpl.java @@ -1,6 +1,7 @@ // Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.codeInsight; +import com.intellij.codeInsight.annoPackages.*; import com.intellij.codeInspection.dataFlow.HardcodedContracts; import com.intellij.openapi.components.PersistentStateComponent; import com.intellij.openapi.components.State; @@ -19,6 +20,7 @@ import com.intellij.psi.util.PsiModificationTracker; import com.intellij.psi.util.PsiUtilCore; import com.intellij.util.containers.ContainerUtil; import gnu.trove.THashSet; +import one.util.streamex.StreamEx; import org.jdom.Element; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -30,13 +32,22 @@ import static com.intellij.codeInsight.AnnotationUtil.NULLABLE; @State(name = "NullableNotNullManager") public class NullableNotNullManagerImpl extends NullableNotNullManager implements PersistentStateComponent, ModificationTracker { - public static final String TYPE_QUALIFIER_NICKNAME = "javax.annotation.meta.TypeQualifierNickname"; private static final String INSTRUMENTED_NOT_NULLS_TAG = "instrumentedNotNulls"; + private final AnnotationPackageSupport[] myAnnotationSupports = { + new JetBrainsAnnotationsSupport(), new FindBugsAnnotationsSupport(), new AndroidAnnotationsSupport(), + new Jsr305Support(this), new CheckerFrameworkSupport()}; + + private final List myDefaultNullables = + StreamEx.of(myAnnotationSupports).toFlatList(s -> s.getNullabilityAnnotations(Nullability.NULLABLE)); + private final List myDefaultNotNulls = + StreamEx.of(myAnnotationSupports).toFlatList(s -> s.getNullabilityAnnotations(Nullability.NOT_NULL)); + private final List myDefaultAll = StreamEx.of(myAnnotationSupports) + .flatCollection(s -> s.getNullabilityAnnotations(Nullability.UNKNOWN)).prepend(myDefaultNotNulls).prepend(myDefaultNullables).toList(); public String myDefaultNullable = NULLABLE; public String myDefaultNotNull = NOT_NULL; - public final JDOMExternalizableStringList myNullables = new JDOMExternalizableStringList(Arrays.asList(DEFAULT_NULLABLES)); - public final JDOMExternalizableStringList myNotNulls = new JDOMExternalizableStringList(Arrays.asList(DEFAULT_NOT_NULLS)); + public final JDOMExternalizableStringList myNullables = new JDOMExternalizableStringList(myDefaultNullables); + public final JDOMExternalizableStringList myNotNulls = new JDOMExternalizableStringList(myDefaultNotNulls); private List myInstrumentedNotNulls = ContainerUtil.newArrayList(NOT_NULL); private final SimpleModificationTracker myTracker = new SimpleModificationTracker(); @@ -84,6 +95,24 @@ public class NullableNotNullManagerImpl extends NullableNotNullManager implement myTracker.incModificationCount(); } + @Override + @NotNull + List getDefaultNullables() { + return myDefaultNullables; + } + + @Override + @NotNull + List getDefaultNotNulls() { + return myDefaultNotNulls; + } + + @Override + @NotNull + List getAllDefaultAnnotations() { + return myDefaultAll; + } + @Override @NotNull public List getNullables() { @@ -142,8 +171,8 @@ public class NullableNotNullManagerImpl extends NullableNotNullManager implement private boolean hasDefaultValues() { return NOT_NULL.equals(myDefaultNotNull) && NULLABLE.equals(myDefaultNullable) && - new HashSet<>(myNullables).equals(ContainerUtil.newHashSet(DEFAULT_NULLABLES)) && - new HashSet<>(myNotNulls).equals(ContainerUtil.newHashSet(DEFAULT_NOT_NULLS)); + new HashSet<>(myNullables).equals(new HashSet<>(getDefaultNullables())) && + new HashSet<>(myNotNulls).equals(new HashSet<>(getDefaultNotNulls())); } @Override @@ -166,24 +195,24 @@ public class NullableNotNullManagerImpl extends NullableNotNullManager implement } private void normalizeDefaults() { - myNotNulls.removeAll(ContainerUtil.newHashSet(DEFAULT_NULLABLES)); - myNullables.removeAll(ContainerUtil.newHashSet(DEFAULT_NOT_NULLS)); - myNullables.addAll(ContainerUtil.filter(DEFAULT_NULLABLES, s -> !myNullables.contains(s))); - myNotNulls.addAll(ContainerUtil.filter(DEFAULT_NOT_NULLS, s -> !myNotNulls.contains(s))); + myNotNulls.removeAll(getDefaultNullables()); + myNullables.removeAll(getDefaultNotNulls()); + myNullables.addAll(ContainerUtil.filter(getDefaultNullables(), s -> !myNullables.contains(s))); + myNotNulls.addAll(ContainerUtil.filter(getDefaultNotNulls(), s -> !myNotNulls.contains(s))); myTracker.incModificationCount(); } @NotNull private List getAllNullabilityNickNames() { - if (!getNotNulls().contains(JAVAX_ANNOTATION_NONNULL)) { + if (!getNotNulls().contains(Jsr305Support.JAVAX_ANNOTATION_NONNULL)) { return Collections.emptyList(); } return CachedValuesManager.getManager(myProject).getCachedValue(myProject, () -> { List result = new ArrayList<>(); GlobalSearchScope scope = GlobalSearchScope.allScope(myProject); - PsiClass[] nickDeclarations = JavaPsiFacade.getInstance(myProject).findClasses(TYPE_QUALIFIER_NICKNAME, scope); + PsiClass[] nickDeclarations = JavaPsiFacade.getInstance(myProject).findClasses(Jsr305Support.TYPE_QUALIFIER_NICKNAME, scope); for (PsiClass tqNick : nickDeclarations) { - result.addAll(ContainerUtil.findAll(MetaAnnotationUtil.getChildren(tqNick, scope), NullableNotNullManagerImpl::isNullabilityNickName)); + result.addAll(ContainerUtil.findAll(MetaAnnotationUtil.getChildren(tqNick, scope), Jsr305Support::isNullabilityNickName)); } if (nickDeclarations.length == 0) { result.addAll(getUnresolvedNicknameUsages()); @@ -196,12 +225,13 @@ public class NullableNotNullManagerImpl extends NullableNotNullManager implement @NotNull private List getUnresolvedNicknameUsages() { List result = new ArrayList<>(); - Collection annotations = JavaAnnotationIndex.getInstance().get(StringUtil.getShortName(TYPE_QUALIFIER_NICKNAME), myProject, GlobalSearchScope.allScope(myProject)); + Collection annotations = JavaAnnotationIndex.getInstance().get(StringUtil.getShortName( + Jsr305Support.TYPE_QUALIFIER_NICKNAME), myProject, GlobalSearchScope.allScope(myProject)); for (PsiAnnotation annotation : annotations) { PsiElement context = annotation.getContext(); if (context instanceof PsiModifierList && context.getContext() instanceof PsiClass) { PsiClass ownerClass = (PsiClass)context.getContext(); - if (ownerClass.isAnnotationType() && isNullabilityNickName(ownerClass)) { + if (ownerClass.isAnnotationType() && Jsr305Support.isNullabilityNickName(ownerClass)) { result.add(ownerClass); } } @@ -209,27 +239,6 @@ public class NullableNotNullManagerImpl extends NullableNotNullManager implement return result; } - @Nullable - protected NullabilityAnnotationInfo isJsr305Default(@NotNull PsiAnnotation annotation, @NotNull PsiAnnotation.TargetType[] placeTargetTypes) { - PsiClass declaration = resolveAnnotationType(annotation); - PsiModifierList modList = declaration == null ? null : declaration.getModifierList(); - if (modList == null) return null; - - PsiAnnotation tqDefault = AnnotationUtil.findAnnotation(declaration, true, "javax.annotation.meta.TypeQualifierDefault"); - if (tqDefault == null) return null; - - Set required = AnnotationTargetUtil.extractRequiredAnnotationTargets(tqDefault.findAttributeValue(null)); - if (required == null || (!required.isEmpty() && !ContainerUtil.intersects(required, Arrays.asList(placeTargetTypes)))) return null; - - for (PsiAnnotation qualifier : modList.getAnnotations()) { - Nullability nullability = getJsr305QualifierNullability(qualifier); - if (nullability != null) { - return new NullabilityAnnotationInfo(annotation, nullability, true); - } - } - return null; - } - @Override @Nullable NullabilityAnnotationInfo getNullityDefault(@NotNull PsiModifierListOwner container, @@ -260,65 +269,18 @@ public class NullableNotNullManagerImpl extends NullableNotNullManager implement @Nullable private NullabilityAnnotationInfo checkNullityDefault(@NotNull PsiAnnotation annotation, @NotNull PsiAnnotation.TargetType[] placeTargetTypes, boolean superPackage) { - NullabilityAnnotationInfo jsr = superPackage ? null : isJsr305Default(annotation, placeTargetTypes); - return jsr != null ? jsr : CheckerFrameworkNullityUtil.isCheckerDefault(annotation, placeTargetTypes); - } - - @Nullable - private static PsiClass resolveAnnotationType(@NotNull PsiAnnotation annotation) { - PsiJavaCodeReferenceElement element = annotation.getNameReferenceElement(); - PsiElement declaration = element == null ? null : element.resolve(); - if (!(declaration instanceof PsiClass) || !((PsiClass)declaration).isAnnotationType()) return null; - return (PsiClass)declaration; - } - - @Nullable - private Nullability getJsr305QualifierNullability(@NotNull PsiAnnotation qualifier) { - String qName = qualifier.getQualifiedName(); - if (qName == null || !qName.startsWith("javax.annotation.")) return null; - - if (qName.equals(JAVAX_ANNOTATION_NULLABLE) && getNullables().contains(qName)) return Nullability.NULLABLE; - if (qName.equals(JAVAX_ANNOTATION_NONNULL)) return extractNullityFromWhenValue(qualifier); + for (AnnotationPackageSupport support : myAnnotationSupports) { + NullabilityAnnotationInfo info = support.getNullabilityByContainerAnnotation(annotation, placeTargetTypes, superPackage); + if (info != null) { + return info; + } + } return null; } - private static boolean isNullabilityNickName(@NotNull PsiClass candidate) { - String qname = candidate.getQualifiedName(); - if (qname == null || qname.startsWith("javax.annotation.")) return false; - return getNickNamedNullability(candidate) != Nullability.UNKNOWN; - } - - @NotNull - private static Nullability getNickNamedNullability(@NotNull PsiClass psiClass) { - if (AnnotationUtil.findAnnotation(psiClass, TYPE_QUALIFIER_NICKNAME) == null) return Nullability.UNKNOWN; - - PsiAnnotation nonNull = AnnotationUtil.findAnnotation(psiClass, JAVAX_ANNOTATION_NONNULL); - return nonNull != null ? extractNullityFromWhenValue(nonNull) : Nullability.UNKNOWN; - } - - @NotNull - private static Nullability extractNullityFromWhenValue(@NotNull PsiAnnotation nonNull) { - PsiAnnotationMemberValue when = nonNull.findAttributeValue("when"); - if (when instanceof PsiReferenceExpression) { - String refName = ((PsiReferenceExpression)when).getReferenceName(); - if ("ALWAYS".equals(refName)) { - return Nullability.NOT_NULL; - } - if ("MAYBE".equals(refName) || "NEVER".equals(refName)) { - return Nullability.NULLABLE; - } - } - - // 'when' is unknown and annotation is known -> default value (for javax.annotation.Nonnull is ALWAYS) - if (when == null && JAVAX_ANNOTATION_NONNULL.equals(nonNull.getQualifiedName())) { - return Nullability.NOT_NULL; - } - return Nullability.UNKNOWN; - } - @NotNull private List filterNickNames(@NotNull Nullability nullability) { - return ContainerUtil.mapNotNull(getAllNullabilityNickNames(), c -> getNickNamedNullability(c) == nullability ? c.getQualifiedName() : null); + return ContainerUtil.mapNotNull(getAllNullabilityNickNames(), c -> Jsr305Support.getNickNamedNullability(c) == nullability ? c.getQualifiedName() : null); } @NotNull diff --git a/java/java-psi-api/src/com/intellij/codeInsight/NullabilityAnnotationInfo.java b/java/java-psi-api/src/com/intellij/codeInsight/NullabilityAnnotationInfo.java index 06eda217976a..e73f7d5e7d62 100644 --- a/java/java-psi-api/src/com/intellij/codeInsight/NullabilityAnnotationInfo.java +++ b/java/java-psi-api/src/com/intellij/codeInsight/NullabilityAnnotationInfo.java @@ -12,7 +12,7 @@ public class NullabilityAnnotationInfo { private final @NotNull Nullability myNullability; private final boolean myContainer; - NullabilityAnnotationInfo(@NotNull PsiAnnotation annotation, @NotNull Nullability nullability, boolean container) { + public NullabilityAnnotationInfo(@NotNull PsiAnnotation annotation, @NotNull Nullability nullability, boolean container) { myAnnotation = annotation; myNullability = nullability; myContainer = container; diff --git a/java/java-psi-api/src/com/intellij/codeInsight/NullableNotNullManager.java b/java/java-psi-api/src/com/intellij/codeInsight/NullableNotNullManager.java index e430beca2568..06166d99e367 100644 --- a/java/java-psi-api/src/com/intellij/codeInsight/NullableNotNullManager.java +++ b/java/java-psi-api/src/com/intellij/codeInsight/NullableNotNullManager.java @@ -10,13 +10,15 @@ import com.intellij.psi.util.CachedValueProvider; import com.intellij.psi.util.CachedValuesManager; import com.intellij.psi.util.PsiModificationTracker; import com.intellij.psi.util.TypeConversionUtil; -import com.intellij.util.ArrayUtil; import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.*; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; import static com.intellij.codeInsight.AnnotationUtil.*; @@ -27,42 +29,28 @@ public abstract class NullableNotNullManager { protected static final Logger LOG = Logger.getInstance(NullableNotNullManager.class); protected final Project myProject; - protected static final String JAVAX_ANNOTATION_NULLABLE = "javax.annotation.Nullable"; - protected static final String JAVAX_ANNOTATION_NONNULL = "javax.annotation.Nonnull"; - - static final String[] DEFAULT_NULLABLES = { - NULLABLE, - JAVAX_ANNOTATION_NULLABLE, - "javax.annotation.CheckForNull", - "edu.umd.cs.findbugs.annotations.Nullable", - "android.support.annotation.Nullable", - "androidx.annotation.Nullable", - "androidx.annotation.RecentlyNullable", - "org.checkerframework.checker.nullness.qual.Nullable", - "org.checkerframework.checker.nullness.compatqual.NullableDecl", - "org.checkerframework.checker.nullness.compatqual.NullableType", - "com.android.annotations.Nullable", - }; - static final String[] DEFAULT_NOT_NULLS = { - NOT_NULL, - JAVAX_ANNOTATION_NONNULL, - "edu.umd.cs.findbugs.annotations.NonNull", - "android.support.annotation.NonNull", - "androidx.annotation.NonNull", - "androidx.annotation.RecentlyNonNull", - "org.checkerframework.checker.nullness.qual.NonNull", - "org.checkerframework.checker.nullness.compatqual.NonNullDecl", - "org.checkerframework.checker.nullness.compatqual.NonNullType", - "com.android.annotations.NonNull", - }; - private static final List DEFAULT_ALL = Arrays.asList( - ArrayUtil.append(ArrayUtil.mergeArrays(DEFAULT_NULLABLES, DEFAULT_NOT_NULLS), - "org.checkerframework.checker.nullness.qual.MonotonicNonNull")); - protected NullableNotNullManager(Project project) { myProject = project; } + /** + * @return list of default non-container annotations that apply to the nullable element + */ + @NotNull + abstract List getDefaultNullables(); + + /** + * @return list of default non-container annotations that apply to the not-null element + */ + @NotNull + abstract List getDefaultNotNulls(); + + /** + * @return list of all default non-container annotations that affect nullability (including nullable, not-null and unknown) + */ + @NotNull + abstract List getAllDefaultAnnotations(); + public static NullableNotNullManager getInstance(Project project) { return ServiceManager.getService(project, NullableNotNullManager.class); } @@ -179,7 +167,7 @@ public abstract class NullableNotNullManager { if (type == null || TypeConversionUtil.isPrimitiveAndNotNull(type)) return null; // even if javax.annotation.Nullable is not configured, it should still take precedence over ByDefault annotations - List annotations = Arrays.asList(nullable ? DEFAULT_NOT_NULLS : DEFAULT_NULLABLES); + List annotations = nullable ? getDefaultNotNulls() : getDefaultNullables(); int flags = (checkBases ? CHECK_HIERARCHY : 0) | CHECK_EXTERNAL | CHECK_INFERRED | CHECK_TYPE; if (isAnnotated(owner, annotations, flags)) { return null; @@ -260,7 +248,7 @@ public abstract class NullableNotNullManager { @Nullable private NullabilityAnnotationInfo doFindEffectiveNullabilityAnnotation(@NotNull PsiModifierListOwner owner) { Set annotationNames = getAllNullabilityAnnotationsWithNickNames(); - Set extraAnnotations = new HashSet<>(DEFAULT_ALL); + Set extraAnnotations = new HashSet<>(getAllDefaultAnnotations()); extraAnnotations.addAll(annotationNames); PsiAnnotation annotation = findPlainAnnotation(owner, true, extraAnnotations); diff --git a/java/java-psi-api/src/com/intellij/codeInsight/annoPackages/AndroidAnnotationsSupport.java b/java/java-psi-api/src/com/intellij/codeInsight/annoPackages/AndroidAnnotationsSupport.java new file mode 100644 index 000000000000..6f98595f6009 --- /dev/null +++ b/java/java-psi-api/src/com/intellij/codeInsight/annoPackages/AndroidAnnotationsSupport.java @@ -0,0 +1,30 @@ +// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.codeInsight.annoPackages; + +import com.intellij.codeInsight.Nullability; +import org.jetbrains.annotations.NotNull; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +public class AndroidAnnotationsSupport implements AnnotationPackageSupport { + @NotNull + @Override + public List getNullabilityAnnotations(Nullability nullability) { + switch (nullability) { + case NOT_NULL: + return Arrays.asList("android.support.annotation.NonNull", + "androidx.annotation.NonNull", + "androidx.annotation.RecentlyNonNull", + "com.android.annotations.NonNull"); + case NULLABLE: + return Arrays.asList("android.support.annotation.Nullable", + "androidx.annotation.Nullable", + "androidx.annotation.RecentlyNullable", + "com.android.annotations.Nullable"); + default: + return Collections.emptyList(); + } + } +} diff --git a/java/java-psi-api/src/com/intellij/codeInsight/annoPackages/AnnotationPackageSupport.java b/java/java-psi-api/src/com/intellij/codeInsight/annoPackages/AnnotationPackageSupport.java new file mode 100644 index 000000000000..30ad35c6cf95 --- /dev/null +++ b/java/java-psi-api/src/com/intellij/codeInsight/annoPackages/AnnotationPackageSupport.java @@ -0,0 +1,39 @@ +// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.codeInsight.annoPackages; + +import com.intellij.codeInsight.Nullability; +import com.intellij.codeInsight.NullabilityAnnotationInfo; +import com.intellij.psi.PsiAnnotation; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Collections; +import java.util.List; + +/** + * Support for custom annotation packages + */ +public interface AnnotationPackageSupport { + /** + * Returns nullability by a container annotation + * @param anno annotation to check + * @param types target types + * @param superPackage if true then the annotation is applied to the super-package + * @return NullabilityAnnotationInfo object if given annotation is recognized default annotation; null otherwise + */ + @Nullable + default NullabilityAnnotationInfo getNullabilityByContainerAnnotation(PsiAnnotation anno, + PsiAnnotation.TargetType[] types, + boolean superPackage) { + return null; + } + + /** + * @param nullability desired nullability + * @return list of explicit annotations which denote given nullability (and may denote additional semantics) + */ + @NotNull + default List getNullabilityAnnotations(Nullability nullability) { + return Collections.emptyList(); + } +} diff --git a/java/java-psi-api/src/com/intellij/codeInsight/CheckerFrameworkNullityUtil.java b/java/java-psi-api/src/com/intellij/codeInsight/annoPackages/CheckerFrameworkSupport.java similarity index 55% rename from java/java-psi-api/src/com/intellij/codeInsight/CheckerFrameworkNullityUtil.java rename to java/java-psi-api/src/com/intellij/codeInsight/annoPackages/CheckerFrameworkSupport.java index f07c4b05178c..d3aa1912b3ed 100644 --- a/java/java-psi-api/src/com/intellij/codeInsight/CheckerFrameworkNullityUtil.java +++ b/java/java-psi-api/src/com/intellij/codeInsight/annoPackages/CheckerFrameworkSupport.java @@ -1,22 +1,35 @@ -// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. -package com.intellij.codeInsight; +// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.codeInsight.annoPackages; +import com.intellij.codeInsight.AnnotationUtil; +import com.intellij.codeInsight.Nullability; +import com.intellij.codeInsight.NullabilityAnnotationInfo; +import com.intellij.codeInsight.NullableNotNullManager; import com.intellij.psi.*; import com.intellij.psi.util.PsiUtil; import com.intellij.util.containers.ContainerUtil; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; import java.util.Set; /** * @author peter */ -class CheckerFrameworkNullityUtil { +public class CheckerFrameworkSupport implements AnnotationPackageSupport { private static final String DEFAULT_QUALIFIER = "org.checkerframework.framework.qual.DefaultQualifier"; private static final String DEFAULT_QUALIFIERS = "org.checkerframework.framework.qual.DefaultQualifiers"; + public CheckerFrameworkSupport() {} + @Nullable - static NullabilityAnnotationInfo isCheckerDefault(PsiAnnotation anno, PsiAnnotation.TargetType[] types) { + @Override + public NullabilityAnnotationInfo getNullabilityByContainerAnnotation(PsiAnnotation anno, + PsiAnnotation.TargetType[] types, + boolean superPackage) { String qName = anno.getQualifiedName(); if (DEFAULT_QUALIFIER.equals(qName)) { PsiAnnotationMemberValue value = anno.findAttributeValue(PsiAnnotation.DEFAULT_REFERENCED_METHOD_NAME); @@ -35,12 +48,12 @@ class CheckerFrameworkNullityUtil { } return null; } - + if (DEFAULT_QUALIFIERS.equals(qName)) { PsiAnnotationMemberValue value = anno.findAttributeValue(PsiAnnotation.DEFAULT_REFERENCED_METHOD_NAME); for (PsiAnnotationMemberValue initializer : AnnotationUtil.arrayAttributeValues(value)) { if (initializer instanceof PsiAnnotation) { - NullabilityAnnotationInfo result = isCheckerDefault((PsiAnnotation)initializer, types); + NullabilityAnnotationInfo result = getNullabilityByContainerAnnotation((PsiAnnotation)initializer, types, superPackage); if (result != null) { return result; } @@ -51,7 +64,9 @@ class CheckerFrameworkNullityUtil { } private static boolean hasAppropriateTarget(PsiAnnotation.TargetType[] types, PsiAnnotationMemberValue locations) { - Set locationNames = ContainerUtil.map2SetNotNull(AnnotationUtil.arrayAttributeValues(locations), l -> l instanceof PsiReferenceExpression ? ((PsiReferenceExpression)l).getReferenceName() : null); + Set locationNames = ContainerUtil.map2SetNotNull(AnnotationUtil.arrayAttributeValues(locations), + l -> l instanceof PsiReferenceExpression ? ((PsiReferenceExpression)l) + .getReferenceName() : null); if (locationNames.contains("ALL")) return true; for (PsiAnnotation.TargetType type : types) { if (type == PsiAnnotation.TargetType.FIELD) return locationNames.contains("FIELD"); @@ -61,4 +76,21 @@ class CheckerFrameworkNullityUtil { } return false; } + + @NotNull + @Override + public List getNullabilityAnnotations(Nullability nullability) { + switch (nullability) { + case NOT_NULL: + return Arrays.asList("org.checkerframework.checker.nullness.qual.NonNull", + "org.checkerframework.checker.nullness.compatqual.NonNullDecl", + "org.checkerframework.checker.nullness.compatqual.NonNullType"); + case NULLABLE: + return Arrays.asList("org.checkerframework.checker.nullness.qual.Nullable", + "org.checkerframework.checker.nullness.compatqual.NullableDecl", + "org.checkerframework.checker.nullness.compatqual.NullableType"); + default: + return Collections.singletonList("org.checkerframework.checker.nullness.qual.MonotonicNonNull"); + } + } } diff --git a/java/java-psi-api/src/com/intellij/codeInsight/annoPackages/FindBugsAnnotationsSupport.java b/java/java-psi-api/src/com/intellij/codeInsight/annoPackages/FindBugsAnnotationsSupport.java new file mode 100644 index 000000000000..3eddb273e225 --- /dev/null +++ b/java/java-psi-api/src/com/intellij/codeInsight/annoPackages/FindBugsAnnotationsSupport.java @@ -0,0 +1,23 @@ +// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.codeInsight.annoPackages; + +import com.intellij.codeInsight.Nullability; +import org.jetbrains.annotations.NotNull; + +import java.util.Collections; +import java.util.List; + +public class FindBugsAnnotationsSupport implements AnnotationPackageSupport { + @NotNull + @Override + public List getNullabilityAnnotations(Nullability nullability) { + switch (nullability) { + case NOT_NULL: + return Collections.singletonList("edu.umd.cs.findbugs.annotations.NonNull"); + case NULLABLE: + return Collections.singletonList("edu.umd.cs.findbugs.annotations.Nullable"); + default: + return Collections.emptyList(); + } + } +} diff --git a/java/java-psi-api/src/com/intellij/codeInsight/annoPackages/JetBrainsAnnotationsSupport.java b/java/java-psi-api/src/com/intellij/codeInsight/annoPackages/JetBrainsAnnotationsSupport.java new file mode 100644 index 000000000000..e9a91670e9d8 --- /dev/null +++ b/java/java-psi-api/src/com/intellij/codeInsight/annoPackages/JetBrainsAnnotationsSupport.java @@ -0,0 +1,24 @@ +// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.codeInsight.annoPackages; + +import com.intellij.codeInsight.AnnotationUtil; +import com.intellij.codeInsight.Nullability; +import org.jetbrains.annotations.NotNull; + +import java.util.Collections; +import java.util.List; + +public class JetBrainsAnnotationsSupport implements AnnotationPackageSupport { + @NotNull + @Override + public List getNullabilityAnnotations(Nullability nullability) { + switch (nullability) { + case NOT_NULL: + return Collections.singletonList(AnnotationUtil.NOT_NULL); + case NULLABLE: + return Collections.singletonList(AnnotationUtil.NULLABLE); + default: + return Collections.emptyList(); + } + } +} diff --git a/java/java-psi-api/src/com/intellij/codeInsight/annoPackages/Jsr305Support.java b/java/java-psi-api/src/com/intellij/codeInsight/annoPackages/Jsr305Support.java new file mode 100644 index 000000000000..45b3782c87d3 --- /dev/null +++ b/java/java-psi-api/src/com/intellij/codeInsight/annoPackages/Jsr305Support.java @@ -0,0 +1,114 @@ +// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.codeInsight.annoPackages; + +import com.intellij.codeInsight.*; +import com.intellij.psi.*; +import com.intellij.util.containers.ContainerUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +public class Jsr305Support implements AnnotationPackageSupport { + public static final String JAVAX_ANNOTATION_NULLABLE = "javax.annotation.Nullable"; + public static final String JAVAX_ANNOTATION_NONNULL = "javax.annotation.Nonnull"; + public static final String TYPE_QUALIFIER_NICKNAME = "javax.annotation.meta.TypeQualifierNickname"; + private final NullableNotNullManager myManager; + + public Jsr305Support(NullableNotNullManager manager) { + myManager = manager; + } + + @Nullable + @Override + public NullabilityAnnotationInfo getNullabilityByContainerAnnotation(PsiAnnotation annotation, + PsiAnnotation.TargetType[] placeTargetTypes, + boolean superPackage) { + if (superPackage) return null; + PsiClass declaration = resolveAnnotationType(annotation); + PsiModifierList modList = declaration == null ? null : declaration.getModifierList(); + if (modList == null) return null; + + PsiAnnotation tqDefault = AnnotationUtil.findAnnotation(declaration, true, "javax.annotation.meta.TypeQualifierDefault"); + if (tqDefault == null) return null; + + Set required = AnnotationTargetUtil.extractRequiredAnnotationTargets(tqDefault.findAttributeValue(null)); + if (required == null || (!required.isEmpty() && !ContainerUtil.intersects(required, Arrays.asList(placeTargetTypes)))) return null; + + for (PsiAnnotation qualifier : modList.getAnnotations()) { + Nullability nullability = getJsr305QualifierNullability(qualifier); + if (nullability != null) { + return new NullabilityAnnotationInfo(annotation, nullability, true); + } + } + return null; + } + + @Nullable + private Nullability getJsr305QualifierNullability(@NotNull PsiAnnotation qualifier) { + String qName = qualifier.getQualifiedName(); + if (qName == null || !qName.startsWith("javax.annotation.")) return null; + + if (qName.equals(JAVAX_ANNOTATION_NULLABLE) && myManager.getNullables().contains(qName)) return Nullability.NULLABLE; + if (qName.equals(JAVAX_ANNOTATION_NONNULL)) return extractNullityFromWhenValue(qualifier); + return null; + } + + public static boolean isNullabilityNickName(@NotNull PsiClass candidate) { + String qname = candidate.getQualifiedName(); + if (qname == null || qname.startsWith("javax.annotation.")) return false; + return getNickNamedNullability(candidate) != Nullability.UNKNOWN; + } + + @NotNull + public static Nullability getNickNamedNullability(@NotNull PsiClass psiClass) { + if (AnnotationUtil.findAnnotation(psiClass, TYPE_QUALIFIER_NICKNAME) == null) return Nullability.UNKNOWN; + + PsiAnnotation nonNull = AnnotationUtil.findAnnotation(psiClass, JAVAX_ANNOTATION_NONNULL); + return nonNull != null ? extractNullityFromWhenValue(nonNull) : Nullability.UNKNOWN; + } + + @NotNull + private static Nullability extractNullityFromWhenValue(@NotNull PsiAnnotation nonNull) { + PsiAnnotationMemberValue when = nonNull.findAttributeValue("when"); + if (when instanceof PsiReferenceExpression) { + String refName = ((PsiReferenceExpression)when).getReferenceName(); + if ("ALWAYS".equals(refName)) { + return Nullability.NOT_NULL; + } + if ("MAYBE".equals(refName) || "NEVER".equals(refName)) { + return Nullability.NULLABLE; + } + } + + // 'when' is unknown and annotation is known -> default value (for javax.annotation.Nonnull is ALWAYS) + if (when == null && JAVAX_ANNOTATION_NONNULL.equals(nonNull.getQualifiedName())) { + return Nullability.NOT_NULL; + } + return Nullability.UNKNOWN; + } + + @Nullable + private static PsiClass resolveAnnotationType(@NotNull PsiAnnotation annotation) { + PsiJavaCodeReferenceElement element = annotation.getNameReferenceElement(); + PsiElement declaration = element == null ? null : element.resolve(); + if (!(declaration instanceof PsiClass) || !((PsiClass)declaration).isAnnotationType()) return null; + return (PsiClass)declaration; + } + + @NotNull + @Override + public List getNullabilityAnnotations(Nullability nullability) { + switch (nullability) { + case NOT_NULL: + return Collections.singletonList(JAVAX_ANNOTATION_NONNULL); + case NULLABLE: + return Arrays.asList(JAVAX_ANNOTATION_NULLABLE, "javax.annotation.CheckForNull"); + default: + return Collections.emptyList(); + } + } +} diff --git a/java/openapi/src/com/intellij/codeInsight/AnnotationsPanel.java b/java/openapi/src/com/intellij/codeInsight/AnnotationsPanel.java index 40ea6a5605af..cb501704e41f 100644 --- a/java/openapi/src/com/intellij/codeInsight/AnnotationsPanel.java +++ b/java/openapi/src/com/intellij/codeInsight/AnnotationsPanel.java @@ -22,8 +22,10 @@ import javax.swing.table.DefaultTableModel; import javax.swing.table.TableCellRenderer; import javax.swing.table.TableColumn; import java.awt.*; +import java.util.ArrayList; +import java.util.HashSet; import java.util.List; -import java.util.*; +import java.util.Set; public class AnnotationsPanel { private final Project myProject; @@ -37,13 +39,13 @@ public class AnnotationsPanel { String name, String defaultAnnotation, List annotations, - String[] defaultAnnotations, + List defaultAnnotations, Set checkedAnnotations, boolean showInstrumentationOptions, boolean showDefaultActions) { myProject = project; myDefaultAnnotation = defaultAnnotation; - myDefaultAnnotations = new HashSet<>(Arrays.asList(defaultAnnotations)); + myDefaultAnnotations = new HashSet<>(defaultAnnotations); myTableModel = new DefaultTableModel() { @Override public boolean isCellEditable(int row, int column) { diff --git a/java/openapi/src/com/intellij/codeInsight/NullableNotNullDialog.java b/java/openapi/src/com/intellij/codeInsight/NullableNotNullDialog.java index ba7f617b58f4..d8a61b62d392 100644 --- a/java/openapi/src/com/intellij/codeInsight/NullableNotNullDialog.java +++ b/java/openapi/src/com/intellij/codeInsight/NullableNotNullDialog.java @@ -37,12 +37,12 @@ public class NullableNotNullDialog extends DialogWrapper { myNullablePanel = new AnnotationsPanel(project, "Nullable", manager.getDefaultNullable(), - manager.getNullables(), NullableNotNullManager.DEFAULT_NULLABLES, + manager.getNullables(), manager.getDefaultNullables(), Collections.emptySet(), false, true); myNotNullPanel = new AnnotationsPanel(project, "NotNull", manager.getDefaultNotNull(), - manager.getNotNulls(), NullableNotNullManager.DEFAULT_NOT_NULLS, + manager.getNotNulls(), manager.getDefaultNotNulls(), new HashSet<>(manager.getInstrumentedNotNulls()), showInstrumentationOptions, true); init();