| /* |
| * Copyright 2000-2014 JetBrains s.r.o. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.intellij.codeInsight; |
| |
| import com.intellij.openapi.components.PersistentStateComponent; |
| import com.intellij.openapi.components.ServiceManager; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.util.DefaultJDOMExternalizer; |
| import com.intellij.openapi.util.InvalidDataException; |
| import com.intellij.openapi.util.JDOMExternalizableStringList; |
| import com.intellij.openapi.util.WriteExternalException; |
| import com.intellij.psi.*; |
| import com.intellij.psi.util.TypeConversionUtil; |
| import com.intellij.util.containers.ContainerUtil; |
| import org.jdom.Element; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.util.*; |
| |
| /** |
| * User: anna |
| * Date: 1/25/11 |
| */ |
| public class NullableNotNullManager implements PersistentStateComponent<Element> { |
| private static final Logger LOG = Logger.getInstance("#" + NullableNotNullManager.class.getName()); |
| |
| public String myDefaultNullable = AnnotationUtil.NULLABLE; |
| public String myDefaultNotNull = AnnotationUtil.NOT_NULL; |
| public final JDOMExternalizableStringList myNullables = new JDOMExternalizableStringList(); |
| public final JDOMExternalizableStringList myNotNulls = new JDOMExternalizableStringList(); |
| |
| private static final String JAVAX_ANNOTATION_NULLABLE = "javax.annotation.Nullable"; |
| private static final String JAVAX_ANNOTATION_NONNULL = "javax.annotation.Nonnull"; |
| |
| public static final String[] DEFAULT_NULLABLES = {AnnotationUtil.NULLABLE, JAVAX_ANNOTATION_NULLABLE, |
| "edu.umd.cs.findbugs.annotations.Nullable", "android.support.annotation.Nullable" |
| }; |
| public static final String[] DEFAULT_NOT_NULLS = {AnnotationUtil.NOT_NULL, JAVAX_ANNOTATION_NONNULL, |
| "edu.umd.cs.findbugs.annotations.NonNull", "android.support.annotation.NonNull" |
| }; |
| |
| public NullableNotNullManager() { |
| Collections.addAll(myNotNulls, DEFAULT_NOT_NULLS); |
| Collections.addAll(myNullables, DEFAULT_NULLABLES); |
| } |
| |
| public static NullableNotNullManager getInstance(Project project) { |
| return ServiceManager.getService(project, NullableNotNullManager.class); |
| } |
| |
| /** |
| * @return if owner has a @NotNull or @Nullable annotation, or is in scope of @ParametersAreNullableByDefault or ParametersAreNonnullByDefault |
| */ |
| public boolean hasNullability(@NotNull PsiModifierListOwner owner) { |
| return isNullable(owner, false) || isNotNull(owner, false); |
| } |
| |
| private static void addAllIfNotPresent(@NotNull Collection<String> collection, @NotNull String... annotations) { |
| for (String annotation : annotations) { |
| LOG.assertTrue(annotation != null); |
| if (!collection.contains(annotation)) { |
| collection.add(annotation); |
| } |
| } |
| } |
| |
| public void setNotNulls(@NotNull String... annotations) { |
| myNotNulls.clear(); |
| addAllIfNotPresent(myNotNulls, DEFAULT_NOT_NULLS); |
| addAllIfNotPresent(myNotNulls, annotations); |
| } |
| |
| public void setNullables(@NotNull String... annotations) { |
| myNullables.clear(); |
| addAllIfNotPresent(myNullables, DEFAULT_NULLABLES); |
| addAllIfNotPresent(myNullables, annotations); |
| } |
| |
| @NotNull |
| public String getDefaultNullable() { |
| return myDefaultNullable; |
| } |
| |
| @Nullable |
| public String getNullable(@NotNull PsiModifierListOwner owner) { |
| PsiAnnotation annotation = getNullableAnnotation(owner, false); |
| return annotation == null ? null : annotation.getQualifiedName(); |
| } |
| |
| @Nullable |
| public PsiAnnotation getNullableAnnotation(@NotNull PsiModifierListOwner owner, boolean checkBases) { |
| return findNullabilityAnnotation(owner, checkBases, true); |
| } |
| |
| public boolean isContainerAnnotation(@NotNull PsiAnnotation anno) { |
| PsiAnnotation.TargetType[] acceptAnyTarget = PsiAnnotation.TargetType.values(); |
| return isNullabilityDefault(anno, true, acceptAnyTarget) || isNullabilityDefault(anno, false, acceptAnyTarget); |
| } |
| |
| public void setDefaultNullable(@NotNull String defaultNullable) { |
| LOG.assertTrue(getNullables().contains(defaultNullable)); |
| myDefaultNullable = defaultNullable; |
| } |
| |
| @NotNull |
| public String getDefaultNotNull() { |
| return myDefaultNotNull; |
| } |
| |
| @Nullable |
| public PsiAnnotation getNotNullAnnotation(@NotNull PsiModifierListOwner owner, boolean checkBases) { |
| return findNullabilityAnnotation(owner, checkBases, false); |
| } |
| |
| @Nullable |
| public String getNotNull(@NotNull PsiModifierListOwner owner) { |
| PsiAnnotation annotation = getNotNullAnnotation(owner, false); |
| return annotation == null ? null : annotation.getQualifiedName(); |
| } |
| |
| public void setDefaultNotNull(@NotNull String defaultNotNull) { |
| LOG.assertTrue(getNotNulls().contains(defaultNotNull)); |
| myDefaultNotNull = defaultNotNull; |
| } |
| |
| @Nullable |
| private PsiAnnotation findNullabilityAnnotation(@NotNull PsiModifierListOwner owner, boolean checkBases, boolean nullable) { |
| Set<String> qNames = ContainerUtil.newHashSet(nullable ? getNullables() : getNotNulls()); |
| PsiAnnotation annotation = checkBases && (owner instanceof PsiClass || owner instanceof PsiMethod) |
| ? AnnotationUtil.findAnnotationInHierarchy(owner, qNames) |
| : AnnotationUtil.findAnnotation(owner, qNames); |
| if (annotation != null) { |
| return annotation; |
| } |
| |
| PsiType type = getOwnerType(owner); |
| if (type == null || TypeConversionUtil.isPrimitiveAndNotNull(type)) return null; |
| |
| // even if javax.annotation.Nullable is not configured, it should still take precedence over ByDefault annotations |
| if (AnnotationUtil.isAnnotated(owner, nullable ? Arrays.asList(DEFAULT_NOT_NULLS) : Arrays.asList(DEFAULT_NULLABLES), checkBases, false)) { |
| return null; |
| } |
| return findNullabilityDefaultInHierarchy(owner, nullable); |
| } |
| |
| @Nullable |
| private static PsiType getOwnerType(PsiModifierListOwner owner) { |
| if (owner instanceof PsiVariable) return ((PsiVariable)owner).getType(); |
| if (owner instanceof PsiMethod) return ((PsiMethod)owner).getReturnType(); |
| return null; |
| } |
| |
| public boolean isNullable(@NotNull PsiModifierListOwner owner, boolean checkBases) { |
| return findNullabilityAnnotation(owner, checkBases, true) != null; |
| } |
| |
| public boolean isNotNull(@NotNull PsiModifierListOwner owner, boolean checkBases) { |
| return findNullabilityAnnotation(owner, checkBases, false) != null; |
| } |
| |
| @Nullable |
| private static PsiAnnotation findNullabilityDefaultInHierarchy(PsiModifierListOwner owner, boolean nullable) { |
| PsiAnnotation.TargetType[] placeTargetTypes = AnnotationTargetUtil.getTargetsForLocation(owner.getModifierList()); |
| |
| PsiElement element = owner.getParent(); |
| while (element != null) { |
| if (element instanceof PsiModifierListOwner) { |
| PsiAnnotation annotation = getNullabilityDefault((PsiModifierListOwner)element, nullable, placeTargetTypes); |
| if (annotation != null) { |
| return annotation; |
| } |
| } |
| |
| if (element instanceof PsiClassOwner) { |
| String packageName = ((PsiClassOwner)element).getPackageName(); |
| PsiPackage psiPackage = JavaPsiFacade.getInstance(element.getProject()).findPackage(packageName); |
| return psiPackage == null ? null : getNullabilityDefault(psiPackage, nullable, placeTargetTypes); |
| } |
| |
| element = element.getContext(); |
| } |
| return null; |
| } |
| |
| private static PsiAnnotation getNullabilityDefault(@NotNull PsiModifierListOwner container, boolean nullable, PsiAnnotation.TargetType[] placeTargetTypes) { |
| PsiModifierList modifierList = container.getModifierList(); |
| if (modifierList == null) return null; |
| for (PsiAnnotation annotation : modifierList.getAnnotations()) { |
| if (isNullabilityDefault(annotation, nullable, placeTargetTypes)) { |
| return annotation; |
| } |
| } |
| return null; |
| } |
| |
| private static boolean isNullabilityDefault(@NotNull PsiAnnotation annotation, boolean nullable, PsiAnnotation.TargetType[] placeTargetTypes) { |
| PsiJavaCodeReferenceElement element = annotation.getNameReferenceElement(); |
| PsiElement declaration = element == null ? null : element.resolve(); |
| if (!(declaration instanceof PsiClass)) return false; |
| |
| if (!AnnotationUtil.isAnnotated((PsiClass)declaration, |
| nullable ? JAVAX_ANNOTATION_NULLABLE : JAVAX_ANNOTATION_NONNULL, |
| false, |
| true)) { |
| return false; |
| } |
| |
| PsiAnnotation tqDefault = AnnotationUtil.findAnnotation((PsiClass)declaration, true, "javax.annotation.meta.TypeQualifierDefault"); |
| if (tqDefault == null) return false; |
| |
| Set<PsiAnnotation.TargetType> required = AnnotationTargetUtil.extractRequiredAnnotationTargets(tqDefault.findAttributeValue(null)); |
| if (required == null) return false; |
| return required.isEmpty() || ContainerUtil.intersects(required, Arrays.asList(placeTargetTypes)); |
| } |
| |
| @NotNull |
| public List<String> getNullables() { |
| return myNullables; |
| } |
| |
| @NotNull |
| public List<String> getNotNulls() { |
| return myNotNulls; |
| } |
| |
| public boolean hasDefaultValues() { |
| if (DEFAULT_NULLABLES.length != getNullables().size() || DEFAULT_NOT_NULLS.length != getNotNulls().size()) { |
| return false; |
| } |
| if (!myDefaultNotNull.equals(AnnotationUtil.NOT_NULL) || !myDefaultNullable.equals(AnnotationUtil.NULLABLE)) { |
| return false; |
| } |
| for (int i = 0; i < DEFAULT_NULLABLES.length; i++) { |
| if (!getNullables().get(i).equals(DEFAULT_NULLABLES[i])) { |
| return false; |
| } |
| } |
| for (int i = 0; i < DEFAULT_NOT_NULLS.length; i++) { |
| if (!getNotNulls().get(i).equals(DEFAULT_NOT_NULLS[i])) { |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| @Override |
| public Element getState() { |
| final Element component = new Element("component"); |
| |
| if (hasDefaultValues()) { |
| return component; |
| } |
| |
| try { |
| DefaultJDOMExternalizer.writeExternal(this, component); |
| } |
| catch (WriteExternalException e) { |
| LOG.error(e); |
| } |
| return component; |
| } |
| |
| @Override |
| public void loadState(Element state) { |
| try { |
| DefaultJDOMExternalizer.readExternal(this, state); |
| if (myNullables.isEmpty()) { |
| Collections.addAll(myNullables, DEFAULT_NULLABLES); |
| } |
| if (myNotNulls.isEmpty()) { |
| Collections.addAll(myNotNulls, DEFAULT_NOT_NULLS); |
| } |
| } |
| catch (InvalidDataException e) { |
| LOG.error(e); |
| } |
| } |
| |
| public static boolean isNullable(@NotNull PsiModifierListOwner owner) { |
| return !isNotNull(owner) && getInstance(owner.getProject()).isNullable(owner, true); |
| } |
| |
| public static boolean isNotNull(@NotNull PsiModifierListOwner owner) { |
| return getInstance(owner.getProject()).isNotNull(owner, true); |
| } |
| } |