diff --git a/java/java-impl/src/com/intellij/codeInsight/ExternalAnnotationsManagerImpl.java b/java/java-impl/src/com/intellij/codeInsight/ExternalAnnotationsManagerImpl.java index 467cca8c342d..ff68699e419f 100644 --- a/java/java-impl/src/com/intellij/codeInsight/ExternalAnnotationsManagerImpl.java +++ b/java/java-impl/src/com/intellij/codeInsight/ExternalAnnotationsManagerImpl.java @@ -1,4 +1,4 @@ -// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +// Copyright 2000-2022 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.CommonBundle; @@ -46,6 +46,7 @@ import com.intellij.openapi.ui.popup.JBPopupFactory; import com.intellij.openapi.ui.popup.PopupStep; import com.intellij.openapi.ui.popup.util.BaseListPopupStep; import com.intellij.openapi.util.Comparing; +import com.intellij.openapi.util.JDOMUtil; import com.intellij.openapi.util.NlsActions; import com.intellij.openapi.util.TextRange; import com.intellij.openapi.util.registry.Registry; @@ -73,7 +74,15 @@ import com.intellij.util.containers.ContainerUtil; import com.intellij.util.messages.MessageBus; import com.intellij.util.messages.MessageBusConnection; import com.intellij.util.ui.OptionsMessageDialog; +import com.intellij.workspaceModel.ide.WorkspaceModelChangeListener; +import com.intellij.workspaceModel.ide.WorkspaceModelTopics; +import com.intellij.workspaceModel.storage.EntityChange; +import com.intellij.workspaceModel.storage.VersionedStorageChange; +import com.intellij.workspaceModel.storage.WorkspaceEntity; +import com.intellij.workspaceModel.storage.bridgeEntities.api.LibraryEntity; +import com.intellij.workspaceModel.storage.bridgeEntities.api.ModuleCustomImlDataEntity; import one.util.streamex.StreamEx; +import org.jdom.Element; import org.jetbrains.annotations.*; import org.xml.sax.SAXParseException; @@ -85,6 +94,7 @@ import java.nio.file.Path; import java.util.List; import java.util.*; import java.util.function.Function; +import java.util.function.Predicate; import java.util.function.Supplier; import java.util.stream.Collectors; @@ -104,9 +114,12 @@ public final class ExternalAnnotationsManagerImpl extends ReadableExternalAnnota myBus = project.getMessageBus(); MessageBusConnection connection = myBus.connect(this); + + WorkspaceModelTopics.getInstance(project).subscribeAfterModuleLoading(connection, new ExternalAnnotationsRootListener()); connection.subscribe(ProjectTopics.PROJECT_ROOTS, new ModuleRootListener() { @Override public void rootsChanged(@NotNull ModuleRootEvent event) { + if (event.isCausedByWorkspaceModelChangesOnly()) return; dropAnnotationsCache(); } }); @@ -1110,4 +1123,59 @@ public final class ExternalAnnotationsManagerImpl extends ReadableExternalAnnota } } } + + private class ExternalAnnotationsRootListener implements WorkspaceModelChangeListener { + @Override + public void changed(@NotNull VersionedStorageChange event) { + if (hasAnnotationRootInChanges(event, LibraryEntity.class, this::hasAnnotationRoot) || + hasAnnotationRootInChanges(event, ModuleCustomImlDataEntity.class, this::hasAnnotationRoot)) { + dropAnnotationsCache(); + } + } + + private boolean hasAnnotationRootInChanges(@NotNull VersionedStorageChange event, + @NotNull Class entityClass, + @NotNull Predicate hasAnnotationRoot) { + for (EntityChange change : event.getChanges(entityClass)) { + if (change instanceof EntityChange.Added) { + if (hasAnnotationRoot.test(((EntityChange.Added)change).getEntity())) { + return true; + } + } + else if (change instanceof EntityChange.Removed) { + if (hasAnnotationRoot.test(((EntityChange.Removed)change).getEntity())) { + return true; + } + } + else if (change instanceof EntityChange.Replaced) { + if (hasAnnotationRoot.test(((EntityChange.Replaced)change).getOldEntity()) || + hasAnnotationRoot.test(((EntityChange.Replaced)change).getNewEntity())) { + return true; + } + } + } + return false; + } + + private boolean hasAnnotationRoot(LibraryEntity e) { + return e.getRoots().stream().anyMatch(root -> "ANNOTATIONS".equals(root.getType().getName())); + } + + private boolean hasAnnotationRoot(ModuleCustomImlDataEntity e) { + String tagCustomData = e.getRootManagerTagCustomData(); + if (tagCustomData != null) { + try { + Element element = JDOMUtil.load(tagCustomData); + Element child = element.getChild("annotation-paths"); + if (child != null && !child.getChildren("root").isEmpty()) { + return true; + } + } + catch (Throwable ex) { + return false; + } + } + return false; + } + } } diff --git a/java/java-tests/testData/codeInsight/externalAnnotations/content/anno/rootRemoved/annotations.xml b/java/java-tests/testData/codeInsight/externalAnnotations/content/anno/rootRemoved/annotations.xml new file mode 100644 index 000000000000..67f0f98fdf37 --- /dev/null +++ b/java/java-tests/testData/codeInsight/externalAnnotations/content/anno/rootRemoved/annotations.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/externalAnnotations/src/rootRemoved/Foo.java b/java/java-tests/testData/codeInsight/externalAnnotations/src/rootRemoved/Foo.java new file mode 100644 index 000000000000..09fe4a706ef4 --- /dev/null +++ b/java/java-tests/testData/codeInsight/externalAnnotations/src/rootRemoved/Foo.java @@ -0,0 +1,3 @@ +package rootRemoved; +public class Foo { +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/AddAnnotationFixTest.java b/java/java-tests/testSrc/com/intellij/java/codeInsight/AddAnnotationFixTest.java index 9a851372ed30..6716e2fe9633 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInsight/AddAnnotationFixTest.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/AddAnnotationFixTest.java @@ -1,4 +1,4 @@ -// Copyright 2000-2020 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. +// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.intellij.java.codeInsight; import com.intellij.codeInsight.AnnotationUtil; @@ -15,10 +15,7 @@ import com.intellij.openapi.application.ex.PathManagerEx; import com.intellij.openapi.command.WriteCommandAction; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.fileEditor.FileDocumentManager; -import com.intellij.openapi.roots.AnnotationOrderRootType; -import com.intellij.openapi.roots.ModifiableRootModel; -import com.intellij.openapi.roots.ModuleRootManager; -import com.intellij.openapi.roots.OrderRootType; +import com.intellij.openapi.roots.*; import com.intellij.openapi.roots.libraries.Library; import com.intellij.openapi.roots.libraries.LibraryTable; import com.intellij.openapi.util.Disposer; @@ -384,6 +381,27 @@ public class AddAnnotationFixTest extends UsefulTestCase { file.refresh(false, false); }); stopListeningAndCheckEvents(); + } + + public void testLibraryAnnotationRootsChanged() throws IOException { + addDefaultLibrary(); + myFixture.configureByFiles("content/anno/p/annotations.xml"); + PsiFile[] files = myFixture.configureByFiles("lib/p/TestDeannotation.java"); + + PsiClass aClass = ((PsiJavaFile)files[0]).getClasses()[0]; + assertNotNull(aClass); + assertTrue(AnnotationUtil.isAnnotated(aClass.getMethods()[0], AnnotationUtil.NOT_NULL, AnnotationUtil.CHECK_EXTERNAL)); + + ModuleRootModificationUtil.updateModel(myFixture.getModule(), model -> { + final LibraryTable libraryTable = model.getModuleLibraryTable(); + Library library = libraryTable.getModifiableModel().getLibraryByName("test"); + Library.ModifiableModel libraryModifiableModel = library.getModifiableModel(); + libraryModifiableModel.removeRoot(VfsUtilCore.pathToUrl(myFixture.getTempDirPath() + "/content/anno"), + AnnotationOrderRootType.getInstance()); + libraryModifiableModel.commit(); + }); + + assertFalse(AnnotationUtil.isAnnotated(aClass.getMethods()[0], AnnotationUtil.NOT_NULL, AnnotationUtil.CHECK_EXTERNAL)); } public void testAnnotationsUpdatedWhenFileEdited() { diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/ExternalAnnotationsTest.java b/java/java-tests/testSrc/com/intellij/java/codeInsight/ExternalAnnotationsTest.java index 90149e44e51a..95d69c4ab78f 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInsight/ExternalAnnotationsTest.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/ExternalAnnotationsTest.java @@ -1,4 +1,4 @@ -// 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. +// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.intellij.java.codeInsight; import com.intellij.codeInsight.intention.AddAnnotationPsiFix; @@ -11,13 +11,17 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.JavaModuleExternalPaths; import com.intellij.openapi.roots.ModuleRootModificationUtil; import com.intellij.openapi.vfs.VfsUtilCore; +import com.intellij.psi.JavaPsiFacade; +import com.intellij.psi.PsiClass; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiMethod; import com.intellij.psi.codeStyle.JavaCodeStyleSettings; +import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.testFramework.UsefulTestCase; import com.intellij.testFramework.builders.JavaModuleFixtureBuilder; import com.intellij.testFramework.fixtures.*; +import com.intellij.util.ArrayUtil; public class ExternalAnnotationsTest extends UsefulTestCase { private CodeInsightTestFixture myFixture; @@ -116,4 +120,18 @@ public class ExternalAnnotationsTest extends UsefulTestCase { true); } + + public void testExternalAnnotationsRootRemoved() { + myFixture.configureByFiles("src/rootRemoved/Foo.java", "content/anno/rootRemoved/annotations.xml"); + Project project = myFixture.getProject(); + PsiClass aClass = JavaPsiFacade.getInstance(project) + .findClass("rootRemoved.Foo", GlobalSearchScope.projectScope(project)); + assertNotNull(aClass); + assertTrue(aClass.isDeprecated()); + ModuleRootModificationUtil.updateModel(myFixture.getModule(), model -> { + final JavaModuleExternalPaths extension = model.getModuleExtension(JavaModuleExternalPaths.class); + extension.setExternalAnnotationUrls(ArrayUtil.EMPTY_STRING_ARRAY); + }); + assertFalse(aClass.isDeprecated()); + } }