attach JDK Annotations automatically on JDK creation to fix IDEA-211771

GitOrigin-RevId: 975cffd646b68abb31a61eb6ac50de264c428cdc
This commit is contained in:
Alexey Kudravtsev
2019-05-01 02:18:26 +03:00
committed by intellij-monorepo-bot
parent d5e8ba9e13
commit 5910039653
19 changed files with 327 additions and 215 deletions

View File

@@ -7,6 +7,7 @@ import com.intellij.codeInsight.ExternalAnnotationsManager;
import com.intellij.codeInsight.daemon.GroupNames;
import com.intellij.codeInspection.*;
import com.intellij.ide.util.treeView.AbstractTreeNode;
import com.intellij.openapi.application.TransactionGuard;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.progress.util.ProgressIndicatorBase;
import com.intellij.openapi.project.Project;
@@ -16,7 +17,6 @@ import com.intellij.openapi.projectRoots.impl.JavaSdkImpl;
import com.intellij.openapi.roots.JdkUtils;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
@@ -52,7 +52,7 @@ import java.util.function.Function;
import java.util.stream.Collectors;
public class MagicConstantInspection extends AbstractBaseJavaLocalInspectionTool {
private static final Key<Boolean> NO_ANNOTATIONS_FOUND = Key.create("REPORTED_NO_ANNOTATIONS_FOUND");
private static final Key<Boolean> ANNOTATIONS_BEING_ATTACHED = Key.create("REPORTED_NO_ANNOTATIONS_FOUND");
private static final CallMapper<AllowedValues> SPECIAL_CASES = new CallMapper<AllowedValues>()
.register(CallMatcher.instanceCall(CommonClassNames.JAVA_UTIL_CALENDAR, "get").parameterTypes("int"),
@@ -103,7 +103,12 @@ public class MagicConstantInspection extends AbstractBaseJavaLocalInspectionTool
return new JavaElementVisitor() {
@Override
public void visitJavaFile(PsiJavaFile file) {
checkAnnotationsJarAttached(file, holder);
if (!(file.getViewProvider() instanceof InjectedFileViewProvider)) {
Runnable fix = getAttachAnnotationsJarFix(file.getProject());
if (fix != null) {
fix.run(); // try to attach automatically
}
}
}
@Override
@@ -178,55 +183,35 @@ public class MagicConstantInspection extends AbstractBaseJavaLocalInspectionTool
@Override
public void cleanup(@NotNull Project project) {
super.cleanup(project);
project.putUserData(NO_ANNOTATIONS_FOUND, null);
project.putUserData(ANNOTATIONS_BEING_ATTACHED, null);
}
private static void checkAnnotationsJarAttached(@NotNull PsiFile file, @NotNull ProblemsHolder holder) {
if (file.getViewProvider() instanceof InjectedFileViewProvider) return;
// returns fix to apply if our own JB "jdkAnnotations" are not attached to the current jdk
public static Runnable getAttachAnnotationsJarFix(Project project) {
final Boolean found = project.getUserData(ANNOTATIONS_BEING_ATTACHED);
if (found != null) return null;
final Project project = file.getProject();
if (!holder.isOnTheFly()) {
final Boolean found = project.getUserData(NO_ANNOTATIONS_FOUND);
if (found != null) return;
}
PsiClass event = JavaPsiFacade.getInstance(project).findClass("java.awt.event.InputEvent", GlobalSearchScope.allScope(project));
if (event == null) return; // no jdk to attach
PsiMethod[] methods = event.findMethodsByName("getModifiers", false);
if (methods.length != 1) return; // no jdk to attach
PsiClass awtInputEvent = JavaPsiFacade.getInstance(project).findClass("java.awt.event.InputEvent", GlobalSearchScope.allScope(project));
if (awtInputEvent == null) return null;
PsiMethod[] methods = awtInputEvent.findMethodsByName("getModifiers", false);
if (methods.length != 1) return null;
PsiMethod getModifiers = methods[0];
PsiAnnotation annotation = ExternalAnnotationsManager.getInstance(project).findExternalAnnotation(getModifiers, MagicConstant.class.getName());
if (annotation != null) return;
Sdk jdk = JdkUtils.getJdkForElement(getModifiers);
if (jdk == null) return; // no jdk to attach
if (jdk == null) return null;
PsiAnnotation annotation = ExternalAnnotationsManager.getInstance(project).findExternalAnnotation(getModifiers, MagicConstant.class.getName());
return annotation == null ? () -> attachAnnotationsLaterTo(project, jdk) : null;
}
if (!holder.isOnTheFly()) {
project.putUserData(NO_ANNOTATIONS_FOUND, Boolean.TRUE);
}
final Sdk finalJdk = jdk;
String path = finalJdk.getHomePath();
String text = "No IDEA annotations attached to the JDK " + finalJdk.getName() + (path == null ? "" : " (" + FileUtil.toSystemDependentName(path) + ")")
+", some issues will not be found";
holder.registerProblem(file, text, ProblemHighlightType.GENERIC_ERROR_OR_WARNING, new LocalQuickFix() {
@NotNull
@Override
public String getFamilyName() {
return "Attach annotations";
}
@Nullable
@Override
public PsiElement getElementToMakeWritable(@NotNull PsiFile file) {
return null;
}
@Override
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
SdkModificator modificator = finalJdk.getSdkModificator();
JavaSdkImpl.attachJdkAnnotations(modificator);
modificator.commitChanges();
private static void attachAnnotationsLaterTo(@NotNull Project project, @NotNull Sdk sdk) {
project.putUserData(ANNOTATIONS_BEING_ATTACHED, Boolean.TRUE);
TransactionGuard.submitTransaction(project, () -> {
SdkModificator modificator = sdk.getSdkModificator();
boolean success = JavaSdkImpl.attachIDEAAnnotationsToJdk(modificator);
// daemon will restart automatically
modificator.commitChanges();
// avoid endless loop on JDK misconfigration
if (success) {
project.putUserData(ANNOTATIONS_BEING_ATTACHED, null);
}
});
}
@@ -416,7 +401,6 @@ public class MagicConstantInspection extends AbstractBaseJavaLocalInspectionTool
@Nullable
static AllowedValues getAllowedValues(@NotNull PsiModifierListOwner element, @Nullable PsiType type, @Nullable Set<? super PsiClass> visited) {
PsiManager manager = element.getManager();
for (PsiAnnotation annotation : getAllAnnotations(element)) {
if (type != null && MagicConstant.class.getName().equals(annotation.getQualifiedName())) {

View File

@@ -263,29 +263,12 @@ public class JavaSdkImpl extends JavaSdk {
}
public static void attachJdkAnnotations(@NotNull SdkModificator modificator) {
LocalFileSystem lfs = LocalFileSystem.getInstance();
attachIDEAAnnotationsToJdk(modificator);
}
public static boolean attachIDEAAnnotationsToJdk(@NotNull SdkModificator modificator) {
List<String> pathsChecked = new ArrayList<>();
// community idea under idea
String path = FileUtil.toSystemIndependentName(PathManager.getCommunityHomePath()) + "/java/jdkAnnotations";
VirtualFile root = lfs.findFileByPath(path);
pathsChecked.add(path);
if (root == null) { // build
String javaPluginClassesRootPath = PathManager.getJarPathForClass(JavaSdkImpl.class);
LOG.assertTrue(javaPluginClassesRootPath != null);
File javaPluginClassesRoot = new File(javaPluginClassesRootPath);
if (javaPluginClassesRoot.isFile()) {
String annotationsJarPath = FileUtil.toSystemIndependentName(new File(javaPluginClassesRoot.getParentFile(), "jdkAnnotations.jar").getAbsolutePath());
root = VirtualFileManager.getInstance().findFileByUrl("jar://" + annotationsJarPath + "!/");
pathsChecked.add(annotationsJarPath);
}
if (root == null) {
String url = "jar://" + FileUtil.toSystemIndependentName(PathManager.getHomePath()) + "/lib/jdkAnnotations.jar!/";
root = VirtualFileManager.getInstance().findFileByUrl(url);
pathsChecked.add(FileUtil.toSystemIndependentName(PathManager.getHomePath()) + "/lib/jdkAnnotations.jar");
}
}
VirtualFile root = internalJdkAnnotationsPath(pathsChecked);
if (root == null) {
StringBuilder msg = new StringBuilder("Paths checked:\n");
@@ -294,12 +277,39 @@ public class JavaSdkImpl extends JavaSdk {
msg.append(p).append("; ").append(f.exists()).append("; ").append(Arrays.toString(f.getParentFile().list())).append('\n');
}
LOG.error("JDK annotations not found", msg.toString());
return;
return false;
}
OrderRootType annoType = AnnotationOrderRootType.getInstance();
modificator.removeRoot(root, annoType);
if (modificator.getRoots(annoType).length != 0) {
modificator.removeRoot(root, annoType);
}
modificator.addRoot(root, annoType);
return true;
}
static VirtualFile internalJdkAnnotationsPath(@NotNull List<? super String> pathsChecked) {
String javaPluginClassesRootPath = PathManager.getJarPathForClass(JavaSdkImpl.class);
LOG.assertTrue(javaPluginClassesRootPath != null);
File javaPluginClassesRoot = new File(javaPluginClassesRootPath);
VirtualFile root = null;
if (javaPluginClassesRoot.isFile()) {
String annotationsJarPath = FileUtil.toSystemIndependentName(new File(javaPluginClassesRoot.getParentFile(), "jdkAnnotations.jar").getAbsolutePath());
root = VirtualFileManager.getInstance().findFileByUrl("jar://" + annotationsJarPath + "!/");
pathsChecked.add(annotationsJarPath);
}
if (root == null) {
String url = "jar://" + FileUtil.toSystemIndependentName(PathManager.getHomePath()) + "/lib/jdkAnnotations.jar!/";
root = VirtualFileManager.getInstance().findFileByUrl(url);
pathsChecked.add(url);
}
if (root == null) {
// community idea under idea
String path = FileUtil.toSystemIndependentName(PathManager.getCommunityHomePath()) + "/java/jdkAnnotations";
root = LocalFileSystem.getInstance().findFileByPath(path);
pathsChecked.add(path);
}
return root;
}
@Override
@@ -348,6 +358,8 @@ public class JavaSdkImpl extends JavaSdk {
addClasses(jdkHomeFile, sdkModificator, isJre);
addSources(jdkHomeFile, sdkModificator);
addDocs(jdkHomeFile, sdkModificator, null);
attachJdkAnnotations(sdkModificator);
sdkModificator.commitChanges();
return jdk;
@@ -370,7 +382,7 @@ public class JavaSdkImpl extends JavaSdk {
@Override public void setVersionString(String versionString) { throw new UnsupportedOperationException(); }
@Override public SdkAdditionalData getSdkAdditionalData() { throw new UnsupportedOperationException(); }
@Override public void setSdkAdditionalData(SdkAdditionalData data) { throw new UnsupportedOperationException(); }
@Override public @NotNull VirtualFile[] getRoots(@NotNull OrderRootType rootType) { throw new UnsupportedOperationException(); }
@Override public @NotNull VirtualFile[] getRoots(@NotNull OrderRootType rootType) { return roots.get(rootType).toArray(VirtualFile.EMPTY_ARRAY); }
@Override public void removeRoot(@NotNull VirtualFile root, @NotNull OrderRootType rootType) { throw new UnsupportedOperationException(); }
@Override public void removeRoots(@NotNull OrderRootType rootType) { throw new UnsupportedOperationException(); }
@Override public void removeAllRoots() { throw new UnsupportedOperationException(); }
@@ -385,6 +397,7 @@ public class JavaSdkImpl extends JavaSdk {
addClasses(jdkHomeFile, sdkModificator, isJre);
addSources(jdkHomeFile, sdkModificator);
attachJdkAnnotations(sdkModificator);
return new MockSdk(jdkName, homePath, jdkName, roots, this);
}
@@ -403,8 +416,8 @@ public class JavaSdkImpl extends JavaSdk {
private static List<String> readModulesFromReleaseFile(File jrtBaseDir) {
File releaseFile = new File(jrtBaseDir, "release");
if (releaseFile.isFile()) {
Properties p = new Properties();
try (FileInputStream stream = new FileInputStream(releaseFile)) {
Properties p = new Properties();
p.load(stream);
String modules = p.getProperty("MODULES");
if (modules != null) {
@@ -419,20 +432,20 @@ public class JavaSdkImpl extends JavaSdk {
}
@NotNull
private static List<String> findClasses(@NotNull File file, boolean isJre) {
private static List<String> findClasses(@NotNull File jdkHome, boolean isJre) {
List<String> result = ContainerUtil.newArrayList();
if (JdkUtil.isExplodedModularRuntime(file.getPath())) {
File[] exploded = new File(file, "modules").listFiles();
if (JdkUtil.isExplodedModularRuntime(jdkHome.getPath())) {
File[] exploded = new File(jdkHome, "modules").listFiles();
if (exploded != null) {
for (File root : exploded) {
result.add(VfsUtil.getUrlForLibraryRoot(root));
}
}
}
else if (JdkUtil.isModularRuntime(file)) {
String jrtBaseUrl = JrtFileSystem.PROTOCOL_PREFIX + getPath(file) + JrtFileSystem.SEPARATOR;
List<String> modules = readModulesFromReleaseFile(file);
else if (JdkUtil.isModularRuntime(jdkHome)) {
String jrtBaseUrl = JrtFileSystem.PROTOCOL_PREFIX + getPath(jdkHome) + JrtFileSystem.SEPARATOR;
List<String> modules = readModulesFromReleaseFile(jdkHome);
if (modules != null) {
for (String module : modules) {
result.add(jrtBaseUrl + module);
@@ -448,7 +461,7 @@ public class JavaSdkImpl extends JavaSdk {
}
}
else {
for (File root : JavaSdkUtil.getJdkClassesRoots(file, isJre)) {
for (File root : JavaSdkUtil.getJdkClassesRoots(jdkHome, isJre)) {
result.add(VfsUtil.getUrlForLibraryRoot(root));
}
}

View File

@@ -37,7 +37,7 @@ public class Example {
@NotNull Class x = <warning descr="'null' is assigned to a variable that is annotated with @NotNull">ClassUtils.primitiveToWrapper(null)</warning>;
}
void writeBytes(@Nullable byte[] bytes) throws IOException {
new FilterOutputStream(null).write(<warning descr="Argument 'bytes' might be null">bytes</warning>);
void writeBytes(@Nullable byte[] bytes, FilterOutputStream stream) throws IOException {
stream.write(<warning descr="Argument 'bytes' might be null">bytes</warning>);
}
}

View File

@@ -1,3 +1,5 @@
import org.jetbrains.annotations.NotNull;
import java.util.*;
class Main {
@@ -14,6 +16,7 @@ class Main {
newMethod();
}
@NotNull
private Object[] newMethod() {
return myScheduledUpdates.keySet().toArray(new Object[myScheduledUpdates.keySet().size()]);
}

View File

@@ -380,7 +380,7 @@ public class ParameterInfoTest extends AbstractParameterInfoTestCase {
public void testInferredWithVarargs() {
myFixture.configureByText(JavaFileType.INSTANCE,
"import java.util.*; class C { void m(Object objects[], List<Object> list) { Collections.addAll(<caret>list, objects);} }");
assertEquals("<html>Collection&lt;? super Object&gt; collection, @NotNull Object... ts</html>", parameterPresentation(-1));
assertEquals("<html>@NotNull Collection&lt;? super Object&gt; collection,</html><html> @NotNull Object... ts</html>", parameterPresentation(-1));
}
private void checkHighlighted(int lineIndex) {
@@ -450,7 +450,7 @@ public class ParameterInfoTest extends AbstractParameterInfoTestCase {
"-\n" +
"<html><b>double v</b></html>\n" +
"-\n" +
"<html><b>char[] chars</b></html>\n" +
"<html><b>@NotNull char[] chars</b></html>\n" +
"-\n" +
"<html><b>@Nullable String s</b></html>\n" +
"-\n" +

View File

@@ -615,7 +615,7 @@ public class CompletionHintsTest extends AbstractParameterInfoTestCase {
checkHintContents("<html><b>String</b>&nbsp;&nbsp;<i>a default value. </i></html>");
showParameterInfo();
waitForAllAsyncStuff();
checkHintContents("<html><font color=a8a8a8>@NotNull String key</font></html>\n" +
checkHintContents("<html><font color=a8a8a8>@NonNls @NotNull String key</font></html>\n" +
"-\n" +
"[<html>@NotNull String key, <b>String def</b></html>]");
}
@@ -627,14 +627,14 @@ public class CompletionHintsTest extends AbstractParameterInfoTestCase {
complete("setProperty");
waitForAllAsyncStuff();
checkResultWithInlays("class C { void m() { System.setProperty(<HINT text=\"key:\"/><caret>, <Hint text=\"value:\"/>) } }");
checkHintContents("<html><b>@NotNull String</b>&nbsp;&nbsp;<i>the name of the system property. </i></html>");
checkHintContents("<html><b>@NonNls @NotNull String</b>&nbsp;&nbsp;<i>the name of the system property. </i></html>");
next();
waitForAllAsyncStuff();
checkResultWithInlays("class C { void m() { System.setProperty(<Hint text=\"key:\"/>, <HINT text=\"value:\"/><caret>) } }");
checkHintContents("<html><b>String</b>&nbsp;&nbsp;<i>the value of the system property. </i></html>");
checkHintContents("<html><b>@NonNls @NotNull String</b>&nbsp;&nbsp;<i>the value of the system property. </i></html>");
showParameterInfo();
waitForAllAsyncStuff();
checkHintContents("<html>@NotNull String key, <b>String value</b></html>");
checkHintContents("<html>@NonNls @NotNull String key, <b>@NonNls @NotNull String value</b></html>");
}
public void testUpInEditor() {
@@ -646,7 +646,7 @@ public class CompletionHintsTest extends AbstractParameterInfoTestCase {
checkHintContents("<html><b>@NotNull String</b>&nbsp;&nbsp;<i>the name of the system property. </i></html>");
showParameterInfo();
waitForAllAsyncStuff();
checkHintContents("<html><b>@NotNull String key</b></html>\n" +
checkHintContents("<html><b>@NonNls @NotNull String key</b></html>\n" +
"-\n" +
"[<html><b>@NotNull String key</b>, String def</html>]");
up();
@@ -664,7 +664,7 @@ public class CompletionHintsTest extends AbstractParameterInfoTestCase {
checkHintContents("<html><b>@NotNull String</b>&nbsp;&nbsp;<i>the name of the system property. </i></html>");
showParameterInfo();
waitForAllAsyncStuff();
checkHintContents("<html><b>@NotNull String key</b></html>\n" +
checkHintContents("<html><b>@NonNls @NotNull String key</b></html>\n" +
"-\n" +
"[<html><b>@NotNull String key</b>, String def</html>]");
myFixture.performEditorAction(IdeActions.ACTION_EDITOR_MOVE_CARET_DOWN);
@@ -693,7 +693,7 @@ public class CompletionHintsTest extends AbstractParameterInfoTestCase {
configureJava("class C { void m() { System.getPro<caret> } }");
complete("getProperty(String key)");
waitForAllAsyncStuff();
checkHintContents("<html><b>@NotNull String</b>&nbsp;&nbsp;<i>the name of the system property. </i></html>");
checkHintContents("<html><b>@NonNls @NotNull String</b>&nbsp;&nbsp;<i>the name of the system property. </i></html>");
}
public void testSwitchIsPossibleForManuallyEnteredUnmatchedMethodCall() {
@@ -730,7 +730,7 @@ public class CompletionHintsTest extends AbstractParameterInfoTestCase {
complete("getProperty(String key, String def)");
waitForAllAsyncStuff();
showParameterInfo();
checkHintContents("<html><b>@NotNull String key</b></html>\n" +
checkHintContents("<html><b>@NonNls @NotNull String key</b></html>\n" +
"-\n" +
"[<html><b>@NotNull String key</b>, String def</html>]");
escape();
@@ -748,7 +748,7 @@ public class CompletionHintsTest extends AbstractParameterInfoTestCase {
complete("getProperty(String key, String def)");
checkResultWithInlays("class C { void m() { System.getProperty(<caret>) } }");
waitForAllAsyncStuff();
checkHintContents("<html><b>@NotNull String key</b></html>\n" +
checkHintContents("<html><b>@NonNls @NotNull String key</b></html>\n" +
"-\n" +
"[<html><b>@NotNull String key</b>, String def</html>]");
}
@@ -879,7 +879,7 @@ public class CompletionHintsTest extends AbstractParameterInfoTestCase {
complete("format(String format, Object... args)");
checkResultWithInlays("class C { void m() { String.format(<HINT text=\"format:\"/><caret><Hint text=\",args:\"/>) } }");
waitForAllAsyncStuff();
checkHintContents("<html><b>String</b>&nbsp;&nbsp;<i> A format string </i></html>");
checkHintContents("<html><b>@NotNull String</b>&nbsp;&nbsp;<i> A format string </i></html>");
}
public void testBasicScenarioForConstructor() {

View File

@@ -18,7 +18,9 @@ package com.intellij.java.codeInsight.daemon.quickFix;
import com.intellij.codeInsight.daemon.quickFix.LightQuickFixParameterizedTestCase;
import com.intellij.codeInspection.LocalInspectionTool;
import com.intellij.codeInspection.i18n.I18nInspection;
import com.intellij.openapi.application.PathManager;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.pom.java.LanguageLevel;
import org.jetbrains.annotations.NotNull;
@@ -44,6 +46,12 @@ public class I18nQuickFixTest extends LightQuickFixParameterizedTestCase {
myMustBeAvailableAfterInvoke = Comparing.strEqual(testName, "SystemCall.java");
}
@Override
public void runSingle() throws Throwable {
VfsGuardian.guard(FileUtil.toSystemIndependentName(PathManager.getCommunityHomePath()), getTestRootDisposable());
super.runSingle();
}
@Override
protected boolean shouldBeAvailableAfterExecution() {
return myMustBeAvailableAfterInvoke;

View File

@@ -0,0 +1,54 @@
// 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.java.codeInsight.daemon.quickFix;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.WriteAction;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileEvent;
import com.intellij.openapi.vfs.VirtualFileListener;
import com.intellij.openapi.vfs.VirtualFileManager;
import gnu.trove.THashMap;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
import java.util.Map;
class VfsGuardian {
private static final Logger LOG = Logger.getInstance(VfsGuardian.class);
/**
* Listens for changes in files under {@code root} and reverts them back when {@code parent} gets disposed
*/
static void guard(@NotNull String root, @NotNull Disposable parent) {
Map<VirtualFile, byte[]> oldContent = new THashMap<>();
VirtualFileManager.getInstance().addVirtualFileListener(new VirtualFileListener() {
@Override
public void beforeContentsChange(@NotNull VirtualFileEvent event) {
VirtualFile file = event.getFile();
if (file.getPath().startsWith(root)) {
try {
byte[] old = file.contentsToByteArray();
oldContent.put(file, old);
}
catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}, parent);
Disposer.register(parent, () -> {
for (Map.Entry<VirtualFile, byte[]> entry : oldContent.entrySet()) {
VirtualFile file = entry.getKey();
byte[] content = entry.getValue();
try {
LOG.warn("Restoring "+file+" ...");
WriteAction.run(() -> file.setBinaryContent(content));
}
catch (IOException e) {
throw new RuntimeException(e);
}
}
});
}
}

View File

@@ -8,6 +8,7 @@ import com.intellij.codeInsight.daemon.GutterMark;
import com.intellij.codeInspection.bytecodeAnalysis.BytecodeAnalysisConverter;
import com.intellij.codeInspection.bytecodeAnalysis.ClassDataIndexer;
import com.intellij.codeInspection.bytecodeAnalysis.ProjectBytecodeAnalysis;
import com.intellij.codeInspection.dataFlow.JavaMethodContractUtil;
import com.intellij.java.testutil.MavenDependencyUtil;
import com.intellij.openapi.application.ex.PathManagerEx;
import com.intellij.openapi.command.WriteCommandAction;
@@ -31,7 +32,6 @@ import com.intellij.testFramework.PsiTestUtil;
import com.intellij.testFramework.fixtures.DefaultLightProjectDescriptor;
import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase;
import one.util.streamex.EntryStream;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.java.decompiler.IdeaDecompiler;
@@ -46,7 +46,7 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author lambdamix
*/
public class BytecodeAnalysisIntegrationTest extends LightCodeInsightFixtureTestCase {
private static final String ORG_JETBRAINS_ANNOTATIONS_CONTRACT = Contract.class.getName();
private static final String ORG_JETBRAINS_ANNOTATIONS_CONTRACT = JavaMethodContractUtil.ORG_JETBRAINS_ANNOTATIONS_CONTRACT;
private static final String INFERRED_TEST_METHOD =
"org.apache.velocity.util.ExceptionUtils java.lang.Throwable createWithCause(java.lang.Class, java.lang.String, java.lang.Throwable)";
private static final String EXTERNAL_TEST_METHOD = "java.lang.String String(java.lang.String)";
@@ -69,6 +69,9 @@ public class BytecodeAnalysisIntegrationTest extends LightCodeInsightFixtureTest
}
Sdk sdk = model.getSdk();
if (sdk != null) {
// first, remove bundled JDK Annotations because they are too thorough - can't infer them automatically yet
sdk = PsiTestUtil.modifyJdkRoots(sdk, modificator -> modificator.removeRoots(AnnotationOrderRootType.getInstance()));
sdk = PsiTestUtil.addRootsToJdk(sdk, AnnotationOrderRootType.getInstance(), annotationsRoot);
model.setSdk(sdk);
}
@@ -129,57 +132,59 @@ public class BytecodeAnalysisIntegrationTest extends LightCodeInsightFixtureTest
protected void visitSubPackage(PsiPackage aPackage, PsiClass[] classes) {
for (PsiClass aClass : classes) {
for (PsiMethod method : aClass.getMethods()) {
checkMethodAnnotations(method);
}
}
}
private void checkMethodAnnotations(PsiMethod method) {
if (ProjectBytecodeAnalysis.getInstance(getProject()).getKey(method, digest) == null) return;
String methodKey = PsiFormatUtil.getExternalName(method, false, Integer.MAX_VALUE);
if (INFERRED_TEST_METHOD.equals(methodKey)) return;
String externalNotNullMethodAnnotation = findExternalAnnotation(method, AnnotationUtil.NOT_NULL) == null ? "-" : "@NotNull";
String inferredNotNullMethodAnnotation = findInferredAnnotation(method, AnnotationUtil.NOT_NULL) == null ? "-" : "@NotNull";
if (!externalNotNullMethodAnnotation.equals(inferredNotNullMethodAnnotation)) {
diffs.add(methodKey + ": " + externalNotNullMethodAnnotation + " != " + inferredNotNullMethodAnnotation + "\n");
}
String externalNullableMethodAnnotation = findExternalAnnotation(method, AnnotationUtil.NULLABLE) == null ? "-" : "@Nullable";
String inferredNullableMethodAnnotation = findInferredAnnotation(method, AnnotationUtil.NULLABLE) == null ? "-" : "@Nullable";
if (!externalNullableMethodAnnotation.equals(inferredNullableMethodAnnotation)) {
diffs.add(methodKey + ": " + externalNullableMethodAnnotation + " != " + inferredNullableMethodAnnotation + "\n");
}
for (PsiParameter parameter : method.getParameterList().getParameters()) {
String parameterKey = PsiFormatUtil.getExternalName(parameter, false, Integer.MAX_VALUE);
String externalNotNull = findExternalAnnotation(parameter, AnnotationUtil.NOT_NULL) == null ? "-" : "@NotNull";
String inferredNotNull = findInferredAnnotation(parameter, AnnotationUtil.NOT_NULL) == null ? "-" : "@NotNull";
if (!externalNotNull.equals(inferredNotNull)) {
diffs.add(parameterKey + ": " + externalNotNull + " != " + inferredNotNull + "\n");
}
String externalNullable = findExternalAnnotation(parameter, AnnotationUtil.NULLABLE) == null ? "-" : "@Nullable";
String inferredNullable = findInferredAnnotation(parameter, AnnotationUtil.NULLABLE) == null ? "-" : "@Nullable";
if (!externalNullable.equals(inferredNullable)) {
diffs.add(parameterKey + ": " + externalNullable + " != " + inferredNullable + "\n");
}
}
if (!EXTERNAL_TEST_METHOD.equals(methodKey)) {
PsiAnnotation externalContractAnnotation = findExternalAnnotation(method, ORG_JETBRAINS_ANNOTATIONS_CONTRACT);
PsiAnnotation inferredContractAnnotation = findInferredAnnotation(method, ORG_JETBRAINS_ANNOTATIONS_CONTRACT);
String externalContractAnnotationText = externalContractAnnotation == null ? "-" : externalContractAnnotation.getText();
String inferredContractAnnotationText = inferredContractAnnotation == null ? "-" : inferredContractAnnotation.getText();
if (!externalContractAnnotationText.equals(inferredContractAnnotationText)) {
diffs.add(methodKey + ": " + externalContractAnnotationText + " != " + inferredContractAnnotationText + "\n");
checkMethodAnnotations(method, digest, diffs);
}
}
}
};
rootPackage.accept(visitor);
System.err.println(ClassDataIndexer.ourIndexSizeStatistics);
if (!diffs.isEmpty()) {
System.err.println(ClassDataIndexer.ourIndexSizeStatistics);
}
assertEmpty(diffs);
}
private void checkMethodAnnotations(PsiMethod method, MessageDigest digest, List<? super String> diffs) {
if (ProjectBytecodeAnalysis.getInstance(getProject()).getKey(method, digest) == null) return;
String methodKey = PsiFormatUtil.getExternalName(method, false, Integer.MAX_VALUE);
if (INFERRED_TEST_METHOD.equals(methodKey)) return;
String externalNotNullMethodAnnotation = findExternalAnnotation(method, AnnotationUtil.NOT_NULL) == null ? "-" : "@NotNull";
String inferredNotNullMethodAnnotation = findInferredAnnotation(method, AnnotationUtil.NOT_NULL) == null ? "-" : "@NotNull";
if (!externalNotNullMethodAnnotation.equals(inferredNotNullMethodAnnotation)) {
diffs.add(methodKey + ": " + externalNotNullMethodAnnotation + " != " + inferredNotNullMethodAnnotation + "\n");
}
String externalNullableMethodAnnotation = findExternalAnnotation(method, AnnotationUtil.NULLABLE) == null ? "-" : "@Nullable";
String inferredNullableMethodAnnotation = findInferredAnnotation(method, AnnotationUtil.NULLABLE) == null ? "-" : "@Nullable";
if (!externalNullableMethodAnnotation.equals(inferredNullableMethodAnnotation)) {
diffs.add(methodKey + ": " + externalNullableMethodAnnotation + " != " + inferredNullableMethodAnnotation + "\n");
}
for (PsiParameter parameter : method.getParameterList().getParameters()) {
String parameterKey = PsiFormatUtil.getExternalName(parameter, false, Integer.MAX_VALUE);
String externalNotNull = findExternalAnnotation(parameter, AnnotationUtil.NOT_NULL) == null ? "-" : "@NotNull";
String inferredNotNull = findInferredAnnotation(parameter, AnnotationUtil.NOT_NULL) == null ? "-" : "@NotNull";
if (!externalNotNull.equals(inferredNotNull)) {
diffs.add(parameterKey + ": " + externalNotNull + " != " + inferredNotNull + "\n");
}
String externalNullable = findExternalAnnotation(parameter, AnnotationUtil.NULLABLE) == null ? "-" : "@Nullable";
String inferredNullable = findInferredAnnotation(parameter, AnnotationUtil.NULLABLE) == null ? "-" : "@Nullable";
if (!externalNullable.equals(inferredNullable)) {
diffs.add(parameterKey + ": " + externalNullable + " != " + inferredNullable + "\n");
}
}
if (!EXTERNAL_TEST_METHOD.equals(methodKey)) {
PsiAnnotation externalContractAnnotation = findExternalAnnotation(method, ORG_JETBRAINS_ANNOTATIONS_CONTRACT);
PsiAnnotation inferredContractAnnotation = findInferredAnnotation(method, ORG_JETBRAINS_ANNOTATIONS_CONTRACT);
String externalContractAnnotationText = externalContractAnnotation == null ? "-" : externalContractAnnotation.getText();
String inferredContractAnnotationText = inferredContractAnnotation == null ? "-" : inferredContractAnnotation.getText();
if (!externalContractAnnotationText.equals(inferredContractAnnotationText)) {
diffs.add(methodKey + ": " + externalContractAnnotationText + " != " + inferredContractAnnotationText + "\n");
}
}
}
public void _testExportInferredAnnotations() {
PsiPackage rootPackage = JavaPsiFacade.getInstance(getProject()).findPackage("");
assertNotNull(rootPackage);

View File

@@ -1,61 +0,0 @@
// Copyright 2000-2017 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.java.openapi.projectRoots;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.projectRoots.JavaSdk;
import com.intellij.openapi.projectRoots.ProjectJdkTable;
import com.intellij.openapi.projectRoots.impl.ProjectJdkImpl;
import com.intellij.openapi.roots.OrderRootType;
import com.intellij.openapi.util.JDOMUtil;
import com.intellij.openapi.util.ThrowableComputable;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.testFramework.PlatformTestCase;
import org.jdom.Element;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class ProjectJdkTest extends PlatformTestCase {
public void testDoesntCrashOnJdkRootDisappearance() throws Exception {
VirtualFile nDir = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(createTempDir("nroot", true));
String nUrl = nDir.getUrl();
ProjectJdkImpl jdk = WriteCommandAction.runWriteCommandAction(getProject(), (ThrowableComputable<ProjectJdkImpl, Exception>)()->{
ProjectJdkImpl myJdk = (ProjectJdkImpl)ProjectJdkTable.getInstance().createSdk("my", JavaSdk.getInstance());
Element element = JDOMUtil.load("<jdk version=\"2\">\n" +
" <name value=\"1.8\" />\n" +
" <type value=\"JavaSDK\" />\n" +
" <version value=\"java version &quot;1.8.0_152-ea&quot;\" />\n" +
" <homePath value=\"I:/Java/jdk1.8\" />\n" +
" <roots>\n" +
" <classPath>\n" +
" <root type=\"composite\">\n" +
" <root url=\"" + nUrl + "\" type=\"simple\" />\n" +
" </root>\n" +
" </classPath>\n" +
" </roots>\n" +
" <additional />\n" +
" </jdk>\n"
);
myJdk.readExternal(element);
return myJdk;
});
try {
List<String> urls = Arrays.stream(jdk.getRoots(OrderRootType.CLASSES)).peek(v -> assertTrue(v.isValid())).map(VirtualFile::getUrl).collect(Collectors.toList());
assertOrderedEquals(urls, nUrl);
delete(nDir);
assertFalse(nDir.isValid());
urls = Arrays.stream(jdk.getRoots(OrderRootType.CLASSES)).peek(v -> assertTrue(v.isValid())).map(VirtualFile::getUrl).collect(Collectors.toList());
assertEmpty(urls);
}
finally {
WriteCommandAction.runWriteCommandAction(getProject(), ()->ProjectJdkTable.getInstance().removeJdk(jdk));
}
}
}

View File

@@ -0,0 +1,86 @@
// 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.openapi.projectRoots.impl;
import com.intellij.codeInspection.magicConstant.MagicConstantInspection;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.projectRoots.JavaSdk;
import com.intellij.openapi.projectRoots.ProjectJdkTable;
import com.intellij.openapi.projectRoots.SdkType;
import com.intellij.openapi.roots.AnnotationOrderRootType;
import com.intellij.openapi.roots.OrderRootType;
import com.intellij.openapi.util.JDOMUtil;
import com.intellij.openapi.util.ThrowableComputable;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.testFramework.PlatformTestCase;
import com.intellij.util.ObjectUtils;
import org.intellij.lang.annotations.Language;
import org.jdom.Element;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class ProjectJdkTest extends PlatformTestCase {
public void testDoesntCrashOnJdkRootDisappearance() throws Exception {
VirtualFile nDir = ObjectUtils.assertNotNull(LocalFileSystem.getInstance().refreshAndFindFileByIoFile(createTempDir("nroot", true)));
String nUrl = nDir.getUrl();
ProjectJdkImpl jdk = WriteCommandAction.runWriteCommandAction(getProject(), (ThrowableComputable<ProjectJdkImpl, Exception>)()->{
ProjectJdkImpl myJdk = (ProjectJdkImpl)ProjectJdkTable.getInstance().createSdk("my", JavaSdk.getInstance());
@Language("XML")
String s = "<jdk version=\"2\">\n" +
" <name value=\"1.8\" />\n" +
" <type value=\"JavaSDK\" />\n" +
" <version value=\"java version &quot;1.8.0_152-ea&quot;\" />\n" +
" <homePath value=\"I:/Java/jdk1.8\" />\n" +
" <roots>\n" +
" <classPath>\n" +
" <root type=\"composite\">\n" +
" <root url=\"" + nUrl + "\" type=\"simple\" />\n" +
" </root>\n" +
" </classPath>\n" +
" </roots>\n" +
" <additional />\n" +
"</jdk>\n";
Element element = JDOMUtil.load(s);
myJdk.readExternal(element);
return myJdk;
});
try {
List<String> urls = Arrays.stream(jdk.getRoots(OrderRootType.CLASSES)).peek(v -> assertTrue(v.isValid())).map(VirtualFile::getUrl).collect(Collectors.toList());
assertOrderedEquals(urls, nUrl);
delete(nDir);
assertFalse(nDir.isValid());
urls = Arrays.stream(jdk.getRoots(OrderRootType.CLASSES)).peek(v -> assertTrue(v.isValid())).map(VirtualFile::getUrl).collect(Collectors.toList());
assertEmpty(urls);
}
finally {
WriteCommandAction.runWriteCommandAction(getProject(), ()->ProjectJdkTable.getInstance().removeJdk(jdk));
}
}
public void testJdkAnnotationsAttachedAutomaticallyOnJDKCreation() throws Exception {
ProjectJdkImpl jdk = WriteCommandAction.runWriteCommandAction(getProject(), (ThrowableComputable<ProjectJdkImpl, Exception>)()->
(ProjectJdkImpl)ProjectJdkTable.getInstance().createSdk("my", JavaSdk.getInstance()));
((SdkType)jdk.getSdkType()).setupSdkPaths(jdk);
try {
Runnable fix = MagicConstantInspection.getAttachAnnotationsJarFix(getProject());
assertNull(fix);
List<VirtualFile> annotations = Arrays.asList(jdk.getRoots(AnnotationOrderRootType.getInstance()));
assertNotEmpty(annotations);
VirtualFile internalAnnotationsPath = JavaSdkImpl.internalJdkAnnotationsPath(new ArrayList<>());
assertContainsElements(annotations, internalAnnotationsPath);
}
finally {
WriteCommandAction.runWriteCommandAction(getProject(), ()->ProjectJdkTable.getInstance().removeJdk(jdk));
}
}
}

View File

@@ -2,13 +2,21 @@
package com.intellij.testFramework;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.roots.AnnotationOrderRootType;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.pom.java.LanguageLevel;
public class SdkEqualityTest extends LightPlatformTestCase {
public void testSdkEquality() {
Sdk jdk8 = IdeaTestUtil.getMockJdk(LanguageLevel.JDK_1_8.toJavaVersion());
Sdk jdk8_copy = IdeaTestUtil.getMockJdk(LanguageLevel.JDK_1_8.toJavaVersion());
Sdk jdk8_annotated = PsiTestUtil.addJdkAnnotations(jdk8);
String someRandomStrangePath = FileUtil.toSystemIndependentName(PlatformTestUtil.getCommunityPath()) + "/java/jdkAnnotations/javax";
VirtualFile root = LocalFileSystem.getInstance().findFileByPath(someRandomStrangePath);
Sdk jdk8_annotated = PsiTestUtil.addRootsToJdk(jdk8, AnnotationOrderRootType.getInstance(), root);
Sdk jdk11 = IdeaTestUtil.getMockJdk(LanguageLevel.JDK_11.toJavaVersion());
assertTrue(areSdkEqual(jdk8, jdk8));
assertTrue(areSdkEqual(jdk8, jdk8_copy));

View File

@@ -496,6 +496,16 @@ public class PsiTestUtil {
public static Sdk addRootsToJdk(@NotNull Sdk sdk,
@NotNull OrderRootType rootType,
@NotNull VirtualFile... roots) {
return modifyJdkRoots(sdk, sdkModificator -> {
for (VirtualFile root : roots) {
sdkModificator.addRoot(root, rootType);
}
});
}
@NotNull
@Contract(pure=true)
public static Sdk modifyJdkRoots(@NotNull Sdk sdk, Consumer<? super SdkModificator> modifier) {
Sdk clone;
try {
clone = (Sdk)sdk.clone();
@@ -504,9 +514,7 @@ public class PsiTestUtil {
throw new RuntimeException(e);
}
SdkModificator sdkModificator = clone.getSdkModificator();
for (VirtualFile root : roots) {
sdkModificator.addRoot(root, rootType);
}
modifier.accept(sdkModificator);
sdkModificator.commitChanges();
return clone;
}

View File

@@ -70,12 +70,6 @@ object IdeaGuiTestUtil{
if (foundJdk == null) {
ApplicationManager.getApplication().runWriteAction { ProjectJdkTable.getInstance().addJdk(newJdk) }
}
ApplicationManager.getApplication().runWriteAction {
val modificator = newJdk.sdkModificator
JavaSdkImpl.attachJdkAnnotations(modificator)
modificator.commitChanges()
}
}
else {
throw IllegalStateException("The resolved path '" + path.path + "' was not found")

View File

@@ -1,3 +1,5 @@
import org.jetbrains.annotations.NotNull;
class Test1 {
void foo(){}
{
@@ -5,7 +7,7 @@ class Test1 {
String str1 = "effectively final string";
Comparable<String> a = new Comparable<String>() {
@Override
public int compareTo(String o) {
public int compareTo(@NotNull String o) {
System.out.println(str1);
new Runnable() {
@Override

View File

@@ -1,9 +1,11 @@
import org.jetbrains.annotations.NotNull;
class Test1 {
void foo(){}
{
Comparable<String> a = new Comparable<String>() {
@Override
public int compareTo(String o) {
public int compareTo(@NotNull String o) {
new Runnable() {
@Override
public void run() {

View File

@@ -1,9 +1,11 @@
import org.jetbrains.annotations.NotNull;
class Test1<U> {
public static void main(String[] args) {
Comparable<? extends Integer> c = new Comparable<Integer>() {
@Override
public int compareTo(Integer o) {
public int compareTo(@NotNull Integer o) {
return 0;
}
};

View File

@@ -1,3 +1,5 @@
import org.jetbrains.annotations.NotNull
class Foo implements Comparable<Foo> {
def Foo next() {
@@ -9,7 +11,7 @@ class Foo implements Comparable<Foo> {
}
@Override
int compareTo(Foo o) {
int compareTo(@NotNull Foo o) {
<selection>return 0</selection>
}
}

View File

@@ -1,9 +1,11 @@
import org.jetbrains.annotations.NotNull
class Foo implements Comparable<Foo> {
def next() {return this}
def previous() {return this}
@Override
int compareTo(Foo o) {
int compareTo(@NotNull Foo o) {
<selection>return 0</selection>
}
}