mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[java] Refactor LanguageLevelUtil
Moves everything related to API compatability into JdkApiCompatabilityCache and deprecates some utility methods that already have better alternatives. Also adds documentation and cleans up the utility implementations. #IDEA-370797 Fixed GitOrigin-RevId: a04bfee96d3f779b9fcb9d96709e1c686a93b543
This commit is contained in:
committed by
intellij-monorepo-bot
parent
a28baf5235
commit
e4f3444eed
@@ -0,0 +1,155 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.openapi.module;
|
||||
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.components.Service;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.util.io.FileUtil;
|
||||
import com.intellij.pom.java.LanguageLevel;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Stores a compatibility matrix for JDK APIs. The matrix is generated from {@code @since} tags in the JDK source code.
|
||||
*/
|
||||
@Service(Service.Level.APP)
|
||||
public final class JdkApiCompatabilityCache {
|
||||
private static final Logger LOG = Logger.getInstance(JdkApiCompatabilityCache.class);
|
||||
|
||||
private final Map<LanguageLevel, List<String>> cache = new EnumMap<>(LanguageLevel.class);
|
||||
|
||||
public static JdkApiCompatabilityCache getInstance() {
|
||||
return ApplicationManager.getApplication().getService(JdkApiCompatabilityCache.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the first compatible language level for a given {@code member} that is incompatible given the {@code contextLanguageLevel} or
|
||||
* null if the {@code member} is compatible with the provided {@code contextLanguageLevel}. This method uses a pre-generated JDK
|
||||
* compatability matrix that is generated from {@code @since} tags in the JDK source code, meaning that the JDK Javadoc doesn't need to
|
||||
* be available for this method to work correctly.
|
||||
* <p>
|
||||
* Examples:
|
||||
* <ul>
|
||||
* <li>if {@code member} is annotated as {@code @since 9} and the context language level is {@link LanguageLevel#JDK_1_8} this method
|
||||
* will return {@link LanguageLevel#JDK_1_9}.</li>
|
||||
*
|
||||
* <li>if {@code member} is annotated as {@code @since 10} and the context language level is {@link LanguageLevel#JDK_11} this method
|
||||
* will return {@code null}.
|
||||
*
|
||||
* <li>if {@code member} is method annotated as {@code @since 9} and the context language level is {@link LanguageLevel#JDK_1_8}, but
|
||||
* its super method is annotated as {@code @since 8} this method will return null.
|
||||
*
|
||||
* <li>if {@code member} is method annotated as {@code @since 9} and the context language level is {@link LanguageLevel#JDK_11}, but
|
||||
* its super method is annotated as {@code @since 8} this method will return {@link LanguageLevel#JDK_1_8}.
|
||||
*
|
||||
* <li>if {@code member} is not annotated with a {@code @since} tag this method will return null.
|
||||
* </ul>
|
||||
*
|
||||
* @param member The member to find the incompatible language level for
|
||||
* @param contextLanguageLevel The current language level of the context of {@code member}
|
||||
* @return The first compatible language level for {@code member} or null if it is lower or equal than {@code contextLanguageLevel} or
|
||||
* unknown.
|
||||
*/
|
||||
public @Nullable LanguageLevel firstCompatibleLanguageLevel(@NotNull PsiMember member, @NotNull LanguageLevel contextLanguageLevel) {
|
||||
if (member instanceof PsiAnonymousClass) return null;
|
||||
PsiClass containingClass = member.getContainingClass();
|
||||
if (containingClass instanceof PsiAnonymousClass) return null;
|
||||
if (member instanceof PsiClass clazz && PsiUtil.isLocalClass(clazz)) return null;
|
||||
|
||||
List<PsiMember> membersToCheck = new ArrayList<>();
|
||||
membersToCheck.add(member);
|
||||
if (member instanceof PsiMethod method && !method.isConstructor()) {
|
||||
membersToCheck.addAll(Arrays.asList(method.findSuperMethods()));
|
||||
}
|
||||
|
||||
LanguageLevel incompatibleLevelForContext = LanguageLevelUtil.getNextLanguageLevel(contextLanguageLevel);
|
||||
LanguageLevel lowestCompatibleLanguageLevel = null;
|
||||
for (PsiMember checkMember : membersToCheck) {
|
||||
String signature = getSignature(checkMember);
|
||||
if (signature == null) return null;
|
||||
LanguageLevel compatibleLanguageLevelForMember = getIntroducedApiLevel(signature, incompatibleLevelForContext);
|
||||
if (compatibleLanguageLevelForMember == null) return null;
|
||||
if (lowestCompatibleLanguageLevel == null || compatibleLanguageLevelForMember.isLessThan(lowestCompatibleLanguageLevel)) {
|
||||
lowestCompatibleLanguageLevel = compatibleLanguageLevelForMember;
|
||||
}
|
||||
}
|
||||
return lowestCompatibleLanguageLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all the newly introduced APIs for a given {@code languageLevel} and caches the results.
|
||||
*
|
||||
* @param languageLevel to get the newly introduced APis for.
|
||||
* @return the newly introduced APIs in {@code languageLevel} or empty set if the language level is not supported.
|
||||
*/
|
||||
private @NotNull List<String> getIntroducedApis(@NotNull LanguageLevel languageLevel) {
|
||||
String featureString = languageLevel.toJavaVersion().toFeatureString();
|
||||
List<String> result = cache.get(languageLevel);
|
||||
if (result != null) return result;
|
||||
result = Collections.emptyList();
|
||||
URL resource = JdkApiCompatabilityCache.class.getResource("api" + featureString + ".txt");
|
||||
if (resource != null) {
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(resource.openStream(), StandardCharsets.UTF_8))) {
|
||||
result = FileUtil.loadLines(reader);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
LOG.error("Cannot load: " + resource.getFile(), ex);
|
||||
}
|
||||
}
|
||||
cache.put(languageLevel, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param signature The signature, example: "java.util.Iterator#remove()" as specified by {@link #getSignature(PsiMember)}.
|
||||
* @param languageLevel to start the search.
|
||||
* @return The newly introduced API if it appears after or including {@code languageLevel}, or null if it was introduced before
|
||||
* {@code languageLevel}.
|
||||
*/
|
||||
private LanguageLevel getIntroducedApiLevel(@NotNull String signature, @Nullable LanguageLevel languageLevel) {
|
||||
if (languageLevel == null) return null;
|
||||
if (getIntroducedApis(languageLevel).contains(signature)) return languageLevel;
|
||||
return getIntroducedApiLevel(signature, LanguageLevelUtil.getNextLanguageLevel(languageLevel));
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes a {@code member} for storage in apiX.txt files.
|
||||
* <p>
|
||||
* Example: {@code java.net.URLDecoder#decode(java.lang.String;java.nio.charset.Charset;)}
|
||||
*/
|
||||
@ApiStatus.Internal
|
||||
public @Nullable String getSignature(@Nullable PsiMember member) {
|
||||
if (member instanceof PsiClass psiClass) return psiClass.getQualifiedName();
|
||||
if (member instanceof PsiField) {
|
||||
String containingClass = getSignature(member.getContainingClass());
|
||||
return containingClass == null ? null : containingClass + "#" + member.getName();
|
||||
}
|
||||
if (member instanceof PsiMethod method) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,159 +1,100 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.openapi.module;
|
||||
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.roots.LanguageLevelModuleExtension;
|
||||
import com.intellij.openapi.roots.LanguageLevelProjectExtension;
|
||||
import com.intellij.openapi.roots.ModuleRootManager;
|
||||
import com.intellij.openapi.util.io.FileUtil;
|
||||
import com.intellij.pom.java.LanguageLevel;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.PsiMember;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.lang.ref.Reference;
|
||||
import java.lang.ref.SoftReference;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
|
||||
import static com.intellij.reference.SoftReference.dereference;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public final class LanguageLevelUtil {
|
||||
private LanguageLevelUtil() { }
|
||||
|
||||
/**
|
||||
* Returns explicitly specified custom language level for {@code module}, or {@code null} if the module uses 'Project default' language level.
|
||||
* May return {@linkplain LanguageLevel#isUnsupported() unsupported} language level.
|
||||
* @param module to get the language level for.
|
||||
* @return explicitly specified language level for a {@link Module}, or {@code null} if the module uses 'Project default' language level.
|
||||
* May return an {@linkplain LanguageLevel#isUnsupported() unsupported} language level.
|
||||
*/
|
||||
public static @Nullable LanguageLevel getCustomLanguageLevel(@NotNull Module module) {
|
||||
LanguageLevelModuleExtension moduleExtension = ModuleRootManager.getInstance(module).getModuleExtension(LanguageLevelModuleExtension.class);
|
||||
LanguageLevelModuleExtension moduleExtension =
|
||||
ModuleRootManager.getInstance(module).getModuleExtension(LanguageLevelModuleExtension.class);
|
||||
return moduleExtension != null ? moduleExtension.getLanguageLevel() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns effective language level for the module (either custom module level, or project level if module level is not specified).
|
||||
* May return {@linkplain LanguageLevel#isUnsupported() unsupported} language level.
|
||||
* @param module to get the language level for.
|
||||
* @return the effective language level for a {@link Module}, which is either the overridden language level for the module or the project
|
||||
* language level.
|
||||
* May return {@linkplain LanguageLevel#isUnsupported() unsupported} language level.
|
||||
*/
|
||||
public static @NotNull LanguageLevel getEffectiveLanguageLevel(final @NotNull Module module) {
|
||||
ApplicationManager.getApplication().assertReadAccessAllowed();
|
||||
public static @NotNull LanguageLevel getEffectiveLanguageLevel(@NotNull Module module) {
|
||||
LanguageLevel level = getCustomLanguageLevel(module);
|
||||
if (level != null) return level;
|
||||
return LanguageLevelProjectExtension.getInstance(module.getProject()).getLanguageLevel();
|
||||
}
|
||||
|
||||
private static final Map<LanguageLevel, Reference<Set<String>>> ourForbiddenAPI = new EnumMap<>(LanguageLevel.class);
|
||||
|
||||
/**
|
||||
* @deprecated Please use {@link LanguageLevel#getShortText()} instead.
|
||||
*/
|
||||
@Deprecated(forRemoval = true)
|
||||
public static String getJdkName(LanguageLevel languageLevel) {
|
||||
final String presentableText = languageLevel.getPresentableText();
|
||||
return presentableText.substring(0, presentableText.indexOf(' '));
|
||||
return languageLevel.getShortText();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param api The language level to get the next from.
|
||||
* @return Next {@link LanguageLevel} that is not in preview.
|
||||
* @return Next {@link LanguageLevel} that is not in preview or null if there is no language level.
|
||||
*/
|
||||
public static @Nullable LanguageLevel getNextLanguageLevel(@NotNull LanguageLevel api) {
|
||||
final LanguageLevel[] levels = LanguageLevel.values();
|
||||
final int currentLevelId = api.ordinal();
|
||||
for (int i = currentLevelId + 1; i < levels.length; i++) {
|
||||
final LanguageLevel level = levels[i];
|
||||
List<LanguageLevel> levels = LanguageLevel.getEntries();
|
||||
for (LanguageLevel level : levels.subList(api.ordinal() + 1, levels.size())) {
|
||||
if (!level.isPreview()) return level;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static final Map<LanguageLevel, String> ourPresentableShortMessage = new EnumMap<>(LanguageLevel.class);
|
||||
|
||||
static {
|
||||
for (LanguageLevel level : LanguageLevel.values()) {
|
||||
int feature = level.feature() + 1;
|
||||
if (LanguageLevel.forFeature(feature) == null) break;
|
||||
ourPresentableShortMessage.put(level, feature >= 9 ? String.valueOf(feature) : "1." + feature);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the short language-level name like "17" for Java 17 or "1.5" for Java 1.5.
|
||||
*
|
||||
* @param languageLevel The language level for which to retrieve the short name.
|
||||
* @return The short name associated with the specified language level, or null if the language level is not released yet.
|
||||
* @deprecated Please use {@code languageLevel.toJavaVersion().toFeatureString()} instead.
|
||||
*/
|
||||
public static @Nullable String getShortMessage(@NotNull LanguageLevel languageLevel) {
|
||||
return ourPresentableShortMessage.get(languageLevel);
|
||||
@Deprecated(forRemoval = true)
|
||||
public static @NotNull String getShortMessage(@NotNull LanguageLevel languageLevel) {
|
||||
return languageLevel.toJavaVersion().toFeatureString();
|
||||
}
|
||||
|
||||
/**
|
||||
* For performance reasons, the forbidden API is pre-generated.
|
||||
* @see com.intellij.jvm.analysis.internal.testFramework.JavaApiUsageGenerator
|
||||
*/
|
||||
private static @Nullable Set<String> getForbiddenApi(@NotNull LanguageLevel languageLevel) {
|
||||
String message = getShortMessage(languageLevel);
|
||||
if (message == null) return null;
|
||||
Reference<Set<String>> ref = ourForbiddenAPI.get(languageLevel);
|
||||
Set<String> result = dereference(ref);
|
||||
if (result == null) {
|
||||
String fileName = "api" + message + ".txt";
|
||||
URL resource = LanguageLevelUtil.class.getResource(fileName);
|
||||
if (resource != null) {
|
||||
result = loadSignatureList(resource);
|
||||
}
|
||||
else {
|
||||
Logger.getInstance(LanguageLevelUtil.class).error("File not found: " + fileName);
|
||||
result = Collections.emptySet();
|
||||
}
|
||||
ourForbiddenAPI.put(languageLevel, new SoftReference<>(result));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param member The {@link PsiMember} to get the language level from
|
||||
* @param member The {@link PsiMember} to get the language level from
|
||||
* @param languageLevel The effective language level
|
||||
* @return The last incompatible language level for a {@link PsiMember} as annotated by the @since javadoc or null if it is unknown.
|
||||
* @return The last incompatible language level for a {@link PsiMember} as annotated by the @since Javadoc or null if it is unknown.
|
||||
* For example, if a method is annotated as @since 9 this method will return {@link LanguageLevel#JDK_1_8}.
|
||||
* @deprecated Please use {@code JdkIncompatibleApiCache.getInstance().getLastIncompatibleLanguageLevel(member, languageLevel)}
|
||||
*/
|
||||
@Deprecated(forRemoval = true)
|
||||
public static @Nullable LanguageLevel getLastIncompatibleLanguageLevel(@NotNull PsiMember member, @NotNull LanguageLevel languageLevel) {
|
||||
if (member instanceof PsiAnonymousClass) return null;
|
||||
PsiClass containingClass = member.getContainingClass();
|
||||
if (containingClass instanceof PsiAnonymousClass) return null;
|
||||
if (member instanceof PsiClass && !(member.getParent() instanceof PsiClass || member.getParent() instanceof PsiFile)) return null;
|
||||
String signature = getSignature(member);
|
||||
if (signature == null) return null;
|
||||
LanguageLevel lastLanguageLevel = getLastIncompatibleLanguageLevelForSignature(signature, languageLevel);
|
||||
if (lastLanguageLevel != null) {
|
||||
if (member instanceof PsiMethod && !((PsiMethod)member).isConstructor()) {
|
||||
LanguageLevel lowestSuperLanguageLevel = lastLanguageLevel;
|
||||
for (PsiMethod method : ((PsiMethod)member).findSuperMethods()) {
|
||||
String superSignature = getSignature(method);
|
||||
if (superSignature == null) return null;
|
||||
LanguageLevel lastSuperLanguageLevel = getLastIncompatibleLanguageLevelForSignature(superSignature, languageLevel);
|
||||
if (lastSuperLanguageLevel == null) return null;
|
||||
if (lastSuperLanguageLevel.isLessThan(lowestSuperLanguageLevel)) {
|
||||
lowestSuperLanguageLevel = lastSuperLanguageLevel;
|
||||
}
|
||||
}
|
||||
return lowestSuperLanguageLevel;
|
||||
}
|
||||
}
|
||||
return lastLanguageLevel;
|
||||
}
|
||||
|
||||
private static LanguageLevel getLastIncompatibleLanguageLevelForSignature(@NotNull String signature, @NotNull LanguageLevel languageLevel) {
|
||||
Set<String> forbiddenApi = getForbiddenApi(languageLevel);
|
||||
if (forbiddenApi == null) return null;
|
||||
if (forbiddenApi.contains(signature)) return languageLevel;
|
||||
if (languageLevel.compareTo(LanguageLevel.HIGHEST) == 0) return null;
|
||||
LanguageLevel[] values = LanguageLevel.values();
|
||||
if (languageLevel.ordinal() == values.length - 1) return null;
|
||||
LanguageLevel nextLanguageLevel = values[languageLevel.ordinal() + 1];
|
||||
return getLastIncompatibleLanguageLevelForSignature(signature, nextLanguageLevel);
|
||||
return JdkApiCompatabilityCache.getInstance().firstCompatibleLanguageLevel(member, languageLevel);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Please use {@link JdkApiCompatabilityCache} to check for incompatible APIs.
|
||||
*/
|
||||
@Deprecated(forRemoval = true)
|
||||
public static Set<String> loadSignatureList(@NotNull URL resource) {
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(resource.openStream(), StandardCharsets.UTF_8))) {
|
||||
return new HashSet<>(FileUtil.loadLines(reader));
|
||||
@@ -166,32 +107,11 @@ public final class LanguageLevelUtil {
|
||||
|
||||
/**
|
||||
* For serialization of forbidden api.
|
||||
*
|
||||
* @deprecated Please don't use this, this API was moved to {@link JdkApiCompatabilityCache} and is for internal use only.
|
||||
*/
|
||||
@Deprecated(forRemoval = true)
|
||||
public static @Nullable 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 method) {
|
||||
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;
|
||||
return JdkApiCompatabilityCache.getInstance().getSignature(member);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+3
-3
@@ -1,4 +1,4 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.codeInspection;
|
||||
|
||||
import com.intellij.codeInsight.ExceptionUtil;
|
||||
@@ -7,7 +7,7 @@ import com.intellij.codeInsight.daemon.impl.quickfix.DeleteMultiCatchFix;
|
||||
import com.intellij.java.JavaBundle;
|
||||
import com.intellij.modcommand.ModPsiUpdater;
|
||||
import com.intellij.modcommand.PsiUpdateModCommandQuickFix;
|
||||
import com.intellij.openapi.module.LanguageLevelUtil;
|
||||
import com.intellij.openapi.module.JdkApiCompatabilityCache;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.pom.java.LanguageLevel;
|
||||
@@ -205,7 +205,7 @@ public final class CharsetObjectCanBeUsedInspection extends AbstractBaseJavaLoca
|
||||
PsiMethod[] candidates = method.isConstructor() ? aClass.getConstructors() : aClass.findMethodsByName(method.getName(), false);
|
||||
PsiMethod charsetMethod = Arrays.stream(candidates)
|
||||
.filter(psiMethod -> checkMethod(psiMethod, "java.nio.charset.Charset"))
|
||||
.filter(psiMethod -> LanguageLevelUtil.getLastIncompatibleLanguageLevel(psiMethod, languageLevel) == null)
|
||||
.filter(psiMethod -> JdkApiCompatabilityCache.getInstance().firstCompatibleLanguageLevel(psiMethod, languageLevel) == null)
|
||||
.findFirst().orElse(null);
|
||||
if (charsetMethod == null) return null;
|
||||
return new CharsetMatch(argument, method, charsetMethod);
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.codeInsight;
|
||||
|
||||
import com.intellij.java.JavaBundle;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.application.ReadAction;
|
||||
import com.intellij.openapi.application.ReadActionProcessor;
|
||||
import com.intellij.openapi.module.LanguageLevelUtil;
|
||||
import com.intellij.openapi.module.JdkApiCompatabilityCache;
|
||||
import com.intellij.openapi.progress.EmptyProgressIndicator;
|
||||
import com.intellij.openapi.progress.ProgressIndicator;
|
||||
import com.intellij.openapi.progress.ProgressManager;
|
||||
@@ -151,7 +151,7 @@ public final class FunctionalInterfaceSuggester {
|
||||
@Override
|
||||
public boolean processInReadAction(PsiMember member) {
|
||||
if (member instanceof PsiClass &&
|
||||
LanguageLevelUtil.getLastIncompatibleLanguageLevel(member, PsiUtil.getLanguageLevel(element)) == null) {
|
||||
JdkApiCompatabilityCache.getInstance().firstCompatibleLanguageLevel(member, PsiUtil.getLanguageLevel(element)) == null) {
|
||||
if (!JavaPsiFacade.getInstance(project).getResolveHelper().isAccessible(member, element, null)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.codeInsight.completion;
|
||||
|
||||
import com.intellij.codeInsight.*;
|
||||
@@ -19,7 +19,7 @@ import com.intellij.openapi.editor.Editor;
|
||||
import com.intellij.openapi.editor.RangeMarker;
|
||||
import com.intellij.openapi.editor.ex.EditorSettingsExternalizable;
|
||||
import com.intellij.openapi.fileEditor.FileDocumentManager;
|
||||
import com.intellij.openapi.module.LanguageLevelUtil;
|
||||
import com.intellij.openapi.module.JdkApiCompatabilityCache;
|
||||
import com.intellij.openapi.project.DumbService;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.*;
|
||||
@@ -503,7 +503,7 @@ public final class JavaCompletionUtil {
|
||||
private @Nullable LookupElement generateLookupElementDecorator(@Nullable PsiType qualifierType, @NotNull Object object,
|
||||
@NotNull Function<Consumer<LookupElementPresentation>, LookupElementDecorator<LookupElement>> generator) {
|
||||
if (object instanceof PsiMember) {
|
||||
if (LanguageLevelUtil.getLastIncompatibleLanguageLevel((PsiMember)object, myLanguageLevel) != null) {
|
||||
if (JdkApiCompatabilityCache.getInstance().firstCompatibleLanguageLevel((PsiMember)object, myLanguageLevel) != null) {
|
||||
LookupElementDecorator<LookupElement> element = generator.apply(presentation -> presentation.setItemTextForeground(JBColor.RED));
|
||||
return PrioritizedLookupElement.withExplicitProximity(element, -1);
|
||||
}
|
||||
|
||||
+3
-2
@@ -1,4 +1,4 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.siyeh.ig.inheritance;
|
||||
|
||||
import com.intellij.codeInsight.ExternalAnnotationsManager;
|
||||
@@ -6,6 +6,7 @@ import com.intellij.codeInspection.AnnotateMethodFix;
|
||||
import com.intellij.codeInspection.CleanupLocalInspectionTool;
|
||||
import com.intellij.codeInspection.LocalQuickFix;
|
||||
import com.intellij.codeInspection.options.OptPane;
|
||||
import com.intellij.openapi.module.JdkApiCompatabilityCache;
|
||||
import com.intellij.openapi.module.LanguageLevelUtil;
|
||||
import com.intellij.openapi.module.Module;
|
||||
import com.intellij.openapi.module.ModuleManager;
|
||||
@@ -215,7 +216,7 @@ public class MissingOverrideAnnotationInspection extends BaseInspection implemen
|
||||
@Contract("_, _, _,null -> true")
|
||||
private static boolean ignoreSuperMethod(PsiMethod method, PsiClass methodClass, PsiMethod superMethod, PsiClass superClass) {
|
||||
return !InheritanceUtil.isInheritorOrSelf(methodClass, superClass, true) ||
|
||||
LanguageLevelUtil.getLastIncompatibleLanguageLevel(superMethod, PsiUtil.getLanguageLevel(method)) != null;
|
||||
JdkApiCompatabilityCache.getInstance().firstCompatibleLanguageLevel(superMethod, PsiUtil.getLanguageLevel(method)) != null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -1,4 +1,4 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.siyeh.ig.internationalization;
|
||||
|
||||
import com.intellij.codeInsight.intention.FileModifier.SafeTypeForPreview;
|
||||
@@ -6,7 +6,7 @@ import com.intellij.codeInspection.CleanupLocalInspectionTool;
|
||||
import com.intellij.codeInspection.LocalQuickFix;
|
||||
import com.intellij.modcommand.ModPsiUpdater;
|
||||
import com.intellij.modcommand.PsiUpdateModCommandQuickFix;
|
||||
import com.intellij.openapi.module.LanguageLevelUtil;
|
||||
import com.intellij.openapi.module.JdkApiCompatabilityCache;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.Key;
|
||||
import com.intellij.pom.java.LanguageLevel;
|
||||
@@ -62,7 +62,7 @@ public final class ImplicitDefaultCharsetUsageInspection extends BaseInspection
|
||||
}
|
||||
|
||||
LocalQuickFix createFix(LanguageLevel level) {
|
||||
return myMethod == null || LanguageLevelUtil.getLastIncompatibleLanguageLevel(myMethod, level) != null
|
||||
return myMethod == null || JdkApiCompatabilityCache.getInstance().firstCompatibleLanguageLevel(myMethod, level) != null
|
||||
? null
|
||||
: new AddUtf8CharsetFix(this);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.siyeh.ig.style;
|
||||
|
||||
import com.intellij.codeInsight.options.JavaClassValidator;
|
||||
@@ -8,7 +8,7 @@ import com.intellij.codeInspection.LocalQuickFix;
|
||||
import com.intellij.codeInspection.options.OptPane;
|
||||
import com.intellij.modcommand.ModPsiUpdater;
|
||||
import com.intellij.modcommand.PsiUpdateModCommandQuickFix;
|
||||
import com.intellij.openapi.module.LanguageLevelUtil;
|
||||
import com.intellij.openapi.module.JdkApiCompatabilityCache;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.pom.java.LanguageLevel;
|
||||
import com.intellij.psi.*;
|
||||
@@ -115,7 +115,7 @@ public final class SizeReplaceableByIsEmptyInspection extends BaseInspection {
|
||||
private class SizeReplaceableByIsEmptyVisitor extends BaseInspectionVisitor {
|
||||
private static boolean isLanguageLevelCompatible(PsiElement element, PsiMethod method) {
|
||||
LanguageLevel languageLevel = PsiUtil.getLanguageLevel(element);
|
||||
LanguageLevel lastIncompatibleLanguageLevel = LanguageLevelUtil.getLastIncompatibleLanguageLevel(method, languageLevel);
|
||||
LanguageLevel lastIncompatibleLanguageLevel = JdkApiCompatabilityCache.getInstance().firstCompatibleLanguageLevel(method, languageLevel);
|
||||
return lastIncompatibleLanguageLevel == null || lastIncompatibleLanguageLevel.isLessThan(languageLevel);
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import com.intellij.java.JavaBundle
|
||||
import com.intellij.lang.Language
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.diagnostic.logger
|
||||
import com.intellij.openapi.module.JdkApiCompatabilityCache
|
||||
import com.intellij.openapi.module.LanguageLevelUtil
|
||||
import com.intellij.openapi.module.Module
|
||||
import com.intellij.openapi.module.ModuleUtilCore
|
||||
@@ -25,6 +26,7 @@ import com.intellij.psi.util.InheritanceUtil
|
||||
import com.intellij.psi.util.PsiUtil
|
||||
import com.intellij.testFramework.LightVirtualFile
|
||||
import com.intellij.uast.UastVisitorAdapter
|
||||
import com.siyeh.ig.callMatcher.CallMatcher
|
||||
import org.jdom.Element
|
||||
import org.jetbrains.uast.*
|
||||
|
||||
@@ -51,7 +53,7 @@ class JavaApiUsageInspection : AbstractBaseUastLocalInspectionTool() {
|
||||
private var effectiveLanguageLevel: LanguageLevel? = null
|
||||
|
||||
override fun getOptionsPane(): OptPane {
|
||||
var levels: List<OptDropdown.Option> = LanguageLevel.values().map { option(it.name, it.presentableText) }
|
||||
var levels: List<OptDropdown.Option> = LanguageLevel.entries.map { option(it.name, it.presentableText) }
|
||||
levels = listOf(option("null", JavaBundle.message("label.forbid.api.usages.project"))) + levels
|
||||
return pane(
|
||||
dropdown("effectiveLanguageLevel", JavaBundle.message("label.forbid.api.usages"), *levels.toTypedArray())
|
||||
@@ -91,8 +93,10 @@ class JavaApiUsageInspection : AbstractBaseUastLocalInspectionTool() {
|
||||
private val holder: ProblemsHolder,
|
||||
private val isOnTheFly: Boolean
|
||||
) : ApiUsageUastVisitor(apiUsageProcessor) {
|
||||
private inline val defaultMethods get() = CallMatcher
|
||||
.exactInstanceCall(CommonClassNames.JAVA_UTIL_ITERATOR, "remove")
|
||||
.parameterCount(0)
|
||||
|
||||
private inline val defaultMethods get() = setOf("java.util.Iterator#remove()")
|
||||
private inline val overrideModifierLanguages get() = listOf("kotlin", "scala")
|
||||
|
||||
override fun visitClass(node: UClass): Boolean {
|
||||
@@ -103,13 +107,14 @@ class JavaApiUsageInspection : AbstractBaseUastLocalInspectionTool() {
|
||||
if (!effectiveLanguageLevel.isAtLeast(LanguageLevel.JDK_1_8)) {
|
||||
val version = JavaVersionService.getInstance().getJavaSdkVersion(javaPsi)
|
||||
if (version != null && version.isAtLeast(JavaSdkVersion.JDK_1_8)) {
|
||||
val mSignatures = javaPsi.visibleSignatures.filter { defaultMethods.contains(LanguageLevelUtil.getSignature(it.method)) }
|
||||
if (mSignatures.isNotEmpty()) {
|
||||
val jdkName = LanguageLevelUtil.getJdkName(effectiveLanguageLevel)
|
||||
val message = if (mSignatures.size == 1) {
|
||||
JvmAnalysisBundle.message("jvm.inspections.1.8.problem.single.descriptor", mSignatures.first().name, jdkName)
|
||||
} else {
|
||||
JvmAnalysisBundle.message("jvm.inspections.1.8.problem.descriptor", mSignatures.size, jdkName)
|
||||
val signatures = javaPsi.visibleSignatures.filter { signature -> defaultMethods.methodMatches(signature.method) }
|
||||
if (signatures.isNotEmpty()) {
|
||||
val jdkName = effectiveLanguageLevel.shortText
|
||||
val message = if (signatures.size == 1) {
|
||||
JvmAnalysisBundle.message("jvm.inspections.1.8.problem.single.descriptor", signatures.first().name, jdkName)
|
||||
}
|
||||
else {
|
||||
JvmAnalysisBundle.message("jvm.inspections.1.8.problem.descriptor", signatures.size, jdkName)
|
||||
}
|
||||
holder.registerUProblem(node, message, QuickFixFactory.getInstance().createImplementMethodsFix(javaPsi))
|
||||
}
|
||||
@@ -137,7 +142,7 @@ class JavaApiUsageInspection : AbstractBaseUastLocalInspectionTool() {
|
||||
val module = ModuleUtilCore.findModuleForPsiElement(sourcePsi) ?: return
|
||||
val languageLevel = getEffectiveLanguageLevel(module)
|
||||
val lastIncompatibleLevel = overriddenMethods.mapNotNull { overriddenMethod ->
|
||||
LanguageLevelUtil.getLastIncompatibleLanguageLevel(overriddenMethod, languageLevel)
|
||||
JdkApiCompatabilityCache.getInstance().firstCompatibleLanguageLevel(overriddenMethod, languageLevel)
|
||||
}.minOrNull() ?: return
|
||||
val toHighlight = overrideAnnotation?.uastAnchor?.sourcePsi ?: method.uastAnchor?.sourcePsi ?: return
|
||||
if (shouldReportSinceLevelForElement(lastIncompatibleLevel, sourcePsi) == true) return
|
||||
@@ -151,13 +156,14 @@ class JavaApiUsageInspection : AbstractBaseUastLocalInspectionTool() {
|
||||
private inline val generifiedClasses get() = setOf("javax.swing.JComboBox", "javax.swing.ListModel", "javax.swing.JList")
|
||||
|
||||
override fun processConstructorInvocation(
|
||||
sourceNode: UElement, instantiatedClass: PsiClass, constructor: PsiMethod?, subclassDeclaration: UClass?
|
||||
sourceNode: UElement, instantiatedClass: PsiClass, constructor: PsiMethod?, subclassDeclaration: UClass?,
|
||||
) {
|
||||
constructor ?: return
|
||||
val sourcePsi = sourceNode.sourcePsi ?: return
|
||||
val module = ModuleUtilCore.findModuleForPsiElement(sourcePsi) ?: return
|
||||
val languageLevel = getEffectiveLanguageLevel(module)
|
||||
val lastIncompatibleLevel = LanguageLevelUtil.getLastIncompatibleLanguageLevel(constructor, languageLevel) ?: return
|
||||
val lastIncompatibleLevel = JdkApiCompatabilityCache.getInstance().firstCompatibleLanguageLevel(constructor, languageLevel)
|
||||
?: return
|
||||
if (shouldReportSinceLevelForElement(lastIncompatibleLevel, sourcePsi) == true) return
|
||||
registerError(sourcePsi, lastIncompatibleLevel, holder, isOnTheFly)
|
||||
}
|
||||
@@ -178,7 +184,7 @@ class JavaApiUsageInspection : AbstractBaseUastLocalInspectionTool() {
|
||||
languageLevel = sourcePsi.containingFile.getUserData(PsiUtil.FILE_LANGUAGE_LEVEL_KEY)
|
||||
}
|
||||
if (languageLevel == null) return
|
||||
val lastIncompatibleLevel = LanguageLevelUtil.getLastIncompatibleLanguageLevel(target, languageLevel)
|
||||
val lastIncompatibleLevel = JdkApiCompatabilityCache.getInstance().firstCompatibleLanguageLevel(target, languageLevel)
|
||||
if (lastIncompatibleLevel != null) {
|
||||
if (shouldReportSinceLevelForElement(lastIncompatibleLevel, sourcePsi) == true) return
|
||||
val psiClass = if (qualifier != null) {
|
||||
@@ -200,7 +206,7 @@ class JavaApiUsageInspection : AbstractBaseUastLocalInspectionTool() {
|
||||
if (InheritanceUtil.isInheritor(target, generifiedClass) && !isRawInheritance(generifiedClass, target, mutableSetOf())) {
|
||||
val message = JvmAnalysisBundle.message(
|
||||
"jvm.inspections.1.7.problem.descriptor",
|
||||
LanguageLevelUtil.getJdkName(languageLevel)
|
||||
languageLevel.toJavaVersion().feature
|
||||
)
|
||||
holder.registerProblem(sourcePsi, message)
|
||||
break
|
||||
@@ -239,7 +245,7 @@ class JavaApiUsageInspection : AbstractBaseUastLocalInspectionTool() {
|
||||
}
|
||||
if (reference.getUastParentOfType<UComment>() != null) return
|
||||
val message = JvmAnalysisBundle.message(
|
||||
"jvm.inspections.1.5.problem.descriptor", LanguageLevelUtil.getShortMessage(sinceLanguageLevel)
|
||||
"jvm.inspections.1.5.problem.descriptor", sinceLanguageLevel.toJavaVersion().toFeatureString()
|
||||
)
|
||||
val fix = if (isOnTheFly) {
|
||||
QuickFixFactory.getInstance().createIncreaseLanguageLevelFix(targetLanguageLevel) as LocalQuickFix
|
||||
@@ -248,8 +254,7 @@ class JavaApiUsageInspection : AbstractBaseUastLocalInspectionTool() {
|
||||
holder.registerProblem(reference, message, *LocalQuickFix.notNullElements(fix))
|
||||
}
|
||||
|
||||
fun getEffectiveLanguageLevel(module: Module): LanguageLevel {
|
||||
private fun getEffectiveLanguageLevel(module: Module): LanguageLevel {
|
||||
return effectiveLanguageLevel ?: LanguageLevelUtil.getEffectiveLanguageLevel(module)
|
||||
}
|
||||
|
||||
}
|
||||
+3
-3
@@ -5,7 +5,7 @@ import com.intellij.jvm.analysis.internal.testFramework.JavaApiUsageGenerator.Co
|
||||
import com.intellij.jvm.analysis.internal.testFramework.JavaApiUsageGenerator.Companion.LANGUAGE_LEVEL
|
||||
import com.intellij.jvm.analysis.internal.testFramework.JavaApiUsageGenerator.Companion.PREVIEW_JDK_HOME
|
||||
import com.intellij.jvm.analysis.internal.testFramework.JavaApiUsageGenerator.Companion.SINCE_VERSION
|
||||
import com.intellij.openapi.module.LanguageLevelUtil
|
||||
import com.intellij.openapi.module.JdkApiCompatabilityCache
|
||||
import com.intellij.openapi.projectRoots.JavaSdk
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.intellij.openapi.roots.ContentIterator
|
||||
@@ -173,7 +173,7 @@ class JavaApiUsageGenerator : LightJavaCodeInsightFixtureTestCase() {
|
||||
member.hasAnnotation(JavaPreviewFeatureUtil.JDK_INTERNAL_JAVAC_PREVIEW_FEATURE)
|
||||
}
|
||||
.filter { member -> getLanguageLevel(member) == LANGUAGE_LEVEL }
|
||||
.mapNotNull { LanguageLevelUtil.getSignature(it) }
|
||||
.mapNotNull { JdkApiCompatabilityCache.getInstance().getSignature(it) }
|
||||
)
|
||||
return true
|
||||
}
|
||||
@@ -195,7 +195,7 @@ class JavaApiUsageGenerator : LightJavaCodeInsightFixtureTestCase() {
|
||||
override fun visitElement(element: PsiElement) {
|
||||
super.visitElement(element)
|
||||
if (element is PsiMember && element.isPublicApi()) {
|
||||
val signature = LanguageLevelUtil.getSignature(element) ?: return
|
||||
val signature = JdkApiCompatabilityCache.getInstance().getSignature(element) ?: return
|
||||
val className = signature.substringBefore("#")
|
||||
if (JavaPsiFacade.getInstance(project).findClass(className, GlobalSearchScope.allScope(project)) == null) {
|
||||
return // If the class is not in all scope, don't generate
|
||||
|
||||
+4
-3
@@ -1,6 +1,7 @@
|
||||
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.uiDesigner.inspections;
|
||||
|
||||
import com.intellij.openapi.module.JdkApiCompatabilityCache;
|
||||
import com.intellij.openapi.module.LanguageLevelUtil;
|
||||
import com.intellij.openapi.module.Module;
|
||||
import com.intellij.pom.java.LanguageLevel;
|
||||
@@ -43,8 +44,8 @@ public final class Java15FormInspection extends BaseFormInspection {
|
||||
final PsiMethod getter = PropertyUtilBase.findPropertyGetter(aClass, prop.getName(), false, true);
|
||||
if (getter == null) continue;
|
||||
final LanguageLevel languageLevel = LanguageLevelUtil.getEffectiveLanguageLevel(module);
|
||||
if (LanguageLevelUtil.getLastIncompatibleLanguageLevel(getter, languageLevel) != null) {
|
||||
registerError(component, collector, prop, "@since " + LanguageLevelUtil.getJdkName(languageLevel));
|
||||
if (JdkApiCompatabilityCache.getInstance().firstCompatibleLanguageLevel(getter, languageLevel) != null) {
|
||||
registerError(component, collector, prop, "@since " + languageLevel.toJavaVersion().toFeatureString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user