All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.intellij.codeInspection.java15api.Java15APIUsageInspectionBase Maven / Gradle / Ivy

Go to download

A packaging of the IntelliJ Community Edition java-analysis-impl library. This is release number 1 of trunk branch 142.

The newest version!
/*
 * Copyright 2000-2015 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.codeInspection.java15api;

import com.intellij.ToolExtensionPoints;
import com.intellij.codeHighlighting.HighlightDisplayLevel;
import com.intellij.codeInsight.AnnotationUtil;
import com.intellij.codeInsight.daemon.GroupNames;
import com.intellij.codeInsight.intention.QuickFixFactory;
import com.intellij.codeInspection.*;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.extensions.ExtensionPoint;
import com.intellij.openapi.extensions.ExtensionPointName;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.module.EffectiveLanguageLevelUtil;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleUtilCore;
import com.intellij.openapi.projectRoots.JavaSdkVersion;
import com.intellij.openapi.projectRoots.JavaVersionService;
import com.intellij.openapi.util.InvalidDataException;
import com.intellij.openapi.util.WriteExternalException;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.CharsetToolkit;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.*;
import com.intellij.psi.javadoc.PsiDocComment;
import com.intellij.psi.util.InheritanceUtil;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.reference.SoftReference;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.hash.HashSet;
import gnu.trove.THashSet;
import org.jdom.Element;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.lang.ref.Reference;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * @author max
 */
public class Java15APIUsageInspectionBase extends BaseJavaBatchLocalInspectionTool {
  public static final String SHORT_NAME = "Since15";
  public static final ExtensionPointName EP_NAME =
    ExtensionPointName.create(ToolExtensionPoints.JAVA15_INSPECTION_TOOL);

  private static final String EFFECTIVE_LL = "effectiveLL";

  private static final Map>> ourForbiddenAPI = ContainerUtil.newEnumMap(LanguageLevel.class);
  private static final Set ourIgnored16ClassesAPI = new THashSet(10);
  private static final Map ourPresentableShortMessage = ContainerUtil.newEnumMap(LanguageLevel.class);
  static {
    ourPresentableShortMessage.put(LanguageLevel.JDK_1_3, "1.4");
    ourPresentableShortMessage.put(LanguageLevel.JDK_1_4, "1.5");
    ourPresentableShortMessage.put(LanguageLevel.JDK_1_5, "1.6");
    ourPresentableShortMessage.put(LanguageLevel.JDK_1_6, "1.7");
    ourPresentableShortMessage.put(LanguageLevel.JDK_1_7, "1.8");

    loadForbiddenApi("ignore16List.txt", ourIgnored16ClassesAPI);
  }

  private static final Set ourGenerifiedClasses = new HashSet();
  static {
    ourGenerifiedClasses.add("javax.swing.JComboBox");
    ourGenerifiedClasses.add("javax.swing.ListModel");
    ourGenerifiedClasses.add("javax.swing.JList");
  }
  
  private static final Set ourDefaultMethods = new HashSet();
  static {
    ourDefaultMethods.add("java.util.Iterator#remove()");
  }

  protected LanguageLevel myEffectiveLanguageLevel = null;

  @Nullable
  private static Set getForbiddenApi(@NotNull LanguageLevel languageLevel) {
    if (!ourPresentableShortMessage.containsKey(languageLevel)) return null;
    Reference> ref = ourForbiddenAPI.get(languageLevel);
    Set result = SoftReference.dereference(ref);
    if (result == null) {
      result = new THashSet(1000);
      loadForbiddenApi("api" + getShortName(languageLevel) + ".txt", result);
      ourForbiddenAPI.put(languageLevel, new SoftReference>(result));
    }
    return result;
  }

  private static void loadForbiddenApi(String fileName, Set set) {
    URL resource = Java15APIUsageInspectionBase.class.getResource(fileName);
    if (resource == null) {
      Logger.getInstance(Java15APIUsageInspectionBase.class).warn("not found: " + fileName);
      return;
    }

    try {
      BufferedReader reader = new BufferedReader(new InputStreamReader(resource.openStream(), CharsetToolkit.UTF8_CHARSET));
      try {
        set.addAll(FileUtil.loadLines(reader));
      }
      finally {
        reader.close();
      }
    }
    catch (UnsupportedEncodingException ignored) { }
    catch (IOException ignored) { }
  }

  @Override
  @NotNull
  public String getGroupDisplayName() {
    return GroupNames.LANGUAGE_LEVEL_SPECIFIC_GROUP_NAME;
  }

  @Override
  @NotNull
  public String getDisplayName() {
    return InspectionsBundle.message("inspection.1.5.display.name");
  }

  @Override
  @NotNull
  public String getShortName() {
    return SHORT_NAME;
  }


  @NotNull
  @Override
  public HighlightDisplayLevel getDefaultLevel() {
    return HighlightDisplayLevel.ERROR;
  }

  @Override
  public boolean isEnabledByDefault() {
    return false;
  }

  @Override
  public void readSettings(@NotNull Element node) throws InvalidDataException {
    final Element element = node.getChild(EFFECTIVE_LL);
    if (element != null) {
      myEffectiveLanguageLevel = LanguageLevel.valueOf(element.getAttributeValue("value"));
    }
  }

  @Override
  public void writeSettings(@NotNull Element node) throws WriteExternalException {
    if (myEffectiveLanguageLevel != null) {
      final Element llElement = new Element(EFFECTIVE_LL);
      llElement.setAttribute("value", myEffectiveLanguageLevel.toString());
      node.addContent(llElement);
    }
  }

  @Override
  @NotNull
  public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) {
    return new MyVisitor(holder, isOnTheFly);
  }

  private static boolean isInProject(final PsiElement elt) {
    return elt.getManager().isInProject(elt);
  }

  public static String getShortName(LanguageLevel languageLevel) {
    return ourPresentableShortMessage.get(languageLevel);
  }

  private class MyVisitor extends JavaElementVisitor {
    private final ProblemsHolder myHolder;
    private final boolean myOnTheFly;
    private final ExtensionPoint point = Extensions.getRootArea().getExtensionPoint(EP_NAME);

    public MyVisitor(final ProblemsHolder holder, boolean onTheFly) {
      myHolder = holder;
      myOnTheFly = onTheFly;
    }

    @Override public void visitDocComment(PsiDocComment comment) {
      // No references inside doc comment are of interest.
    }

    @Override public void visitClass(PsiClass aClass) {
      // Don't go into classes (anonymous, locals).
      if (!aClass.hasModifierProperty(PsiModifier.ABSTRACT)) {
        final Module module = ModuleUtilCore.findModuleForPsiElement(aClass);
        final LanguageLevel effectiveLanguageLevel = module != null ? getEffectiveLanguageLevel(module) : null;
        if (effectiveLanguageLevel != null && !effectiveLanguageLevel.isAtLeast(LanguageLevel.JDK_1_8)) {
          final JavaSdkVersion version = JavaVersionService.getInstance().getJavaSdkVersion(aClass);
          if (version != null && version.isAtLeast(JavaSdkVersion.JDK_1_8)) {
            final List methods = new ArrayList();
            for (HierarchicalMethodSignature methodSignature : aClass.getVisibleSignatures()) {
              final PsiMethod method = methodSignature.getMethod();
              if (ourDefaultMethods.contains(getSignature(method))) {
                methods.add(method);
              }
            }
  
            if (!methods.isEmpty()) {
              PsiElement element2Highlight = aClass.getNameIdentifier();
              if (element2Highlight == null) {
                element2Highlight = aClass;
              }
              myHolder.registerProblem(element2Highlight,
                                       methods.size() == 1 ? InspectionsBundle.message("inspection.1.8.problem.single.descriptor", methods.get(0).getName(), getJdkName(effectiveLanguageLevel)) 
                                                           : InspectionsBundle.message("inspection.1.8.problem.descriptor", methods.size(), getJdkName(effectiveLanguageLevel)),
                                       QuickFixFactory.getInstance().createImplementMethodsFix(aClass));
            }
          }
        }
      }
    }

    @Override public void visitReferenceExpression(PsiReferenceExpression expression) {
      visitReferenceElement(expression);
    }

    @Override public void visitReferenceElement(PsiJavaCodeReferenceElement reference) {
      super.visitReferenceElement(reference);
      final PsiElement resolved = reference.resolve();

      if (resolved instanceof PsiCompiledElement && resolved instanceof PsiMember) {
        final Module module = ModuleUtilCore.findModuleForPsiElement(reference.getElement());
        if (module != null) {
          final LanguageLevel languageLevel = getEffectiveLanguageLevel(module);
          if (isForbiddenApiUsage((PsiMember)resolved, languageLevel)) {
            PsiClass psiClass = null;
            final PsiElement qualifier = reference.getQualifier();
            if (qualifier != null) {
              if (qualifier instanceof PsiExpression) {
                psiClass = PsiUtil.resolveClassInType(((PsiExpression)qualifier).getType());
              }
            }
            else {
              psiClass = PsiTreeUtil.getParentOfType(reference, PsiClass.class);
            }
            if (psiClass != null) {
              if (isIgnored(psiClass)) return;
              for (PsiClass superClass : psiClass.getSupers()) {
                if (isIgnored(superClass)) return;
              }
            }
            registerError(reference, languageLevel);
          } else if (resolved instanceof PsiClass && isInProject(reference)&& !languageLevel.isAtLeast(LanguageLevel.JDK_1_7)) {
            final PsiReferenceParameterList parameterList = reference.getParameterList();
            if (parameterList != null && parameterList.getTypeParameterElements().length > 0) {
              for (String generifiedClass : ourGenerifiedClasses) {
                if (InheritanceUtil.isInheritor((PsiClass)resolved, generifiedClass) && 
                    !isRawInheritance(generifiedClass, (PsiClass)resolved, new HashSet())) {
                  String message = InspectionsBundle.message("inspection.1.7.problem.descriptor", getJdkName(languageLevel));
                  myHolder.registerProblem(reference, message);
                  break;
                }
              }
            }
          }
        }
      }
    }

    private boolean isRawInheritance(String generifiedClassQName, PsiClass currentClass, Set visited) {
      for (PsiClassType classType : currentClass.getSuperTypes()) {
        if (classType.isRaw()) {
          return true;
        }
        final PsiClassType.ClassResolveResult resolveResult = classType.resolveGenerics();
        final PsiClass superClass = resolveResult.getElement();
        if (visited.add(superClass) && InheritanceUtil.isInheritor(superClass, generifiedClassQName)) {
          if (isRawInheritance(generifiedClassQName, superClass, visited)) {
            return true;
          }
        }
      }
      return false;
    }

    private boolean isIgnored(PsiClass psiClass) {
      final String qualifiedName = psiClass.getQualifiedName();
      return qualifiedName != null && ourIgnored16ClassesAPI.contains(qualifiedName);
    }

    @Override public void visitNewExpression(final PsiNewExpression expression) {
      super.visitNewExpression(expression);
      final PsiMethod constructor = expression.resolveConstructor();
      final Module module = ModuleUtilCore.findModuleForPsiElement(expression);
      if (module != null) {
        final LanguageLevel languageLevel = getEffectiveLanguageLevel(module);
        if (constructor instanceof PsiCompiledElement) {
          if (isForbiddenApiUsage(constructor, languageLevel)) {
            registerError(expression.getClassReference(), languageLevel);
          }
        }
      }
    }

    @Override
    public void visitMethod(PsiMethod method) {
      super.visitMethod(method);
      PsiAnnotation annotation = AnnotationUtil.findAnnotation(method, CommonClassNames.JAVA_LANG_OVERRIDE);
      if (annotation != null) {
        final Module module = ModuleUtilCore.findModuleForPsiElement(annotation);
        if (module != null) {
          final LanguageLevel languageLevel = getEffectiveLanguageLevel(module);
          final PsiMethod[] methods = method.findSuperMethods();
          for (PsiMethod superMethod : methods) {
            if (superMethod instanceof PsiCompiledElement) {
              if (!isForbiddenApiUsage(superMethod, languageLevel)) {
                return;
              }
            }
            else {
              return;
            }
          }
          if (methods.length > 0) {
            registerError(annotation.getNameReferenceElement(), languageLevel);
          }
        }
      }
    }

    private LanguageLevel getEffectiveLanguageLevel(Module module) {
      if (myEffectiveLanguageLevel != null) return myEffectiveLanguageLevel;
      return EffectiveLanguageLevelUtil.getEffectiveLanguageLevel(module);
    }

    private void registerError(PsiJavaCodeReferenceElement reference, LanguageLevel api) {
      if (reference != null && isInProject(reference)) {
        //noinspection DialogTitleCapitalization
        myHolder.registerProblem(reference, InspectionsBundle.message("inspection.1.5.problem.descriptor", getShortName(api)));
      }
    }

    @Override
    public void visitFile(PsiFile file) {
      for (FileCheckingInspection inspection : point.getExtensions()) {
        ProblemDescriptor[] descriptors = inspection.checkFile(file, InspectionManager.getInstance(file.getProject()), myOnTheFly);
        if (descriptors != null) {
          for (ProblemDescriptor descriptor : descriptors) {
            myHolder.registerProblem(descriptor);
          }
        }
      }
    }
  }

  private static String getJdkName(LanguageLevel languageLevel) {
    final String presentableText = languageLevel.getPresentableText();
    return presentableText.substring(0, presentableText.indexOf(" "));
  }

  public static boolean isForbiddenApiUsage(@NotNull PsiMember member, @NotNull LanguageLevel languageLevel) {
    if (member instanceof PsiAnonymousClass) return false;
    PsiClass containingClass = member.getContainingClass();
    if (containingClass instanceof PsiAnonymousClass) return false;
    if (member instanceof PsiClass && !(member.getParent() instanceof PsiClass || member.getParent() instanceof PsiFile)) return false;

    return isForbiddenSignature(member, languageLevel) ||
           containingClass != null && isForbiddenApiUsage(containingClass, languageLevel);

  }

  private static boolean isForbiddenSignature(@NotNull PsiMember member, @NotNull LanguageLevel languageLevel) {
    Set forbiddenApi = getForbiddenApi(languageLevel);
    String signature = getSignature(member);
    return forbiddenApi != null && signature != null && isForbiddenSignature(signature, languageLevel, forbiddenApi);
  }

  private static boolean isForbiddenSignature(@NotNull String signature, @NotNull LanguageLevel languageLevel, @NotNull Set forbiddenApi) {
    if (forbiddenApi.contains(signature)) {
      return true;
    }
    if (languageLevel.compareTo(LanguageLevel.HIGHEST) == 0) {
      return false;
    }
    LanguageLevel nextLanguageLevel = LanguageLevel.values()[languageLevel.ordinal() + 1];
    Set nextForbiddenApi = getForbiddenApi(nextLanguageLevel);
    return nextForbiddenApi != null && isForbiddenSignature(signature, nextLanguageLevel, nextForbiddenApi);
  }

  /**
   * please leave public for JavaAPIUsagesInspectionTest#testCollectSinceApiUsages
   */
  @Nullable
  public static String getSignature(@Nullable PsiMember member) {
    if (member instanceof PsiClass) {
      return ((PsiClass)member).getQualifiedName();
    }
    if (member instanceof PsiField) {
      String containingClass = getSignature(member.getContainingClass());
      return containingClass == null ? null : containingClass + "#" + member.getName();
    }
    if (member instanceof PsiMethod) {
      final PsiMethod method = (PsiMethod)member;
      String containingClass = getSignature(member.getContainingClass());
      if (containingClass == null) return null;

      StringBuilder buf = new StringBuilder();
      buf.append(containingClass);
      buf.append('#');
      buf.append(method.getName());
      buf.append('(');
      for (PsiType type : method.getSignature(PsiSubstitutor.EMPTY).getParameterTypes()) {
        buf.append(type.getCanonicalText());
        buf.append(";");
      }
      buf.append(')');
      return buf.toString();
    }
    return null;
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy