/* * Copyright 2000-2014 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.intellij.codeInsight; import com.intellij.CommonBundle; import com.intellij.ProjectTopics; import com.intellij.codeInsight.highlighting.HighlightManager; import com.intellij.diagnostic.LogMessageEx; import com.intellij.icons.AllIcons; import com.intellij.ide.DataManager; import com.intellij.ide.highlighter.XmlFileType; import com.intellij.openapi.Disposable; import com.intellij.openapi.application.Application; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.Result; import com.intellij.openapi.command.CommandProcessor; import com.intellij.openapi.command.WriteCommandAction; import com.intellij.openapi.command.undo.BasicUndoableAction; import com.intellij.openapi.command.undo.UndoManager; import com.intellij.openapi.command.undo.UndoUtil; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.LogicalPosition; import com.intellij.openapi.editor.ScrollType; import com.intellij.openapi.editor.colors.EditorColors; import com.intellij.openapi.editor.colors.EditorColorsManager; import com.intellij.openapi.editor.markup.RangeHighlighter; import com.intellij.openapi.editor.markup.TextAttributes; import com.intellij.openapi.fileChooser.FileChooser; import com.intellij.openapi.fileChooser.FileChooserDescriptor; import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory; import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.fileEditor.FileEditorManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.project.ProjectBundle; import com.intellij.openapi.projectRoots.SdkModificator; import com.intellij.openapi.roots.*; import com.intellij.openapi.roots.libraries.Library; import com.intellij.openapi.ui.DialogWrapper; import com.intellij.openapi.ui.Messages; 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.Condition; import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.TextRange; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.*; import com.intellij.psi.*; import com.intellij.psi.codeStyle.CodeStyleSettingsManager; import com.intellij.psi.impl.PsiModificationTrackerImpl; import com.intellij.psi.xml.XmlDocument; import com.intellij.psi.xml.XmlFile; import com.intellij.psi.xml.XmlTag; import com.intellij.util.*; 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 gnu.trove.THashSet; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import java.io.IOException; import java.util.*; import java.util.List; /** * @author anna * @since 26-Jun-2007 */ public class ExternalAnnotationsManagerImpl extends ReadableExternalAnnotationsManager { private static final Logger LOG = Logger.getInstance("#" + ExternalAnnotationsManagerImpl.class.getName()); private final MessageBus myBus; public ExternalAnnotationsManagerImpl(@NotNull final Project project, final PsiManager psiManager) { super(psiManager); myBus = project.getMessageBus(); final MessageBusConnection connection = myBus.connect(project); connection.subscribe(ProjectTopics.PROJECT_ROOTS, new ModuleRootAdapter() { @Override public void rootsChanged(ModuleRootEvent event) { dropCache(); } }); final MyVirtualFileListener fileListener = new MyVirtualFileListener(); VirtualFileManager.getInstance().addVirtualFileListener(fileListener); Disposer.register(myPsiManager.getProject(), new Disposable() { @Override public void dispose() { VirtualFileManager.getInstance().removeVirtualFileListener(fileListener); } }); } private void notifyAfterAnnotationChanging(@NotNull PsiModifierListOwner owner, @NotNull String annotationFQName, boolean successful) { myBus.syncPublisher(TOPIC).afterExternalAnnotationChanging(owner, annotationFQName, successful); ((PsiModificationTrackerImpl)myPsiManager.getModificationTracker()).incCounter(); } private void notifyChangedExternally() { myBus.syncPublisher(TOPIC).externalAnnotationsChangedExternally(); ((PsiModificationTrackerImpl)myPsiManager.getModificationTracker()).incCounter(); } @Override public void annotateExternally(@NotNull final PsiModifierListOwner listOwner, @NotNull final String annotationFQName, @NotNull final PsiFile fromFile, @Nullable final PsiNameValuePair[] value) { ApplicationManager.getApplication().assertIsDispatchThread(); final Project project = myPsiManager.getProject(); final PsiFile containingFile = listOwner.getContainingFile(); if (!(containingFile instanceof PsiJavaFile)) { notifyAfterAnnotationChanging(listOwner, annotationFQName, false); return; } final String packageName = ((PsiJavaFile)containingFile).getPackageName(); final VirtualFile containingVirtualFile = containingFile.getVirtualFile(); LOG.assertTrue(containingVirtualFile != null); final List entries = ProjectRootManager.getInstance(project).getFileIndex().getOrderEntriesForFile(containingVirtualFile); if (entries.isEmpty()) { notifyAfterAnnotationChanging(listOwner, annotationFQName, false); return; } for (final OrderEntry entry : entries) { if (entry instanceof ModuleOrderEntry) continue; VirtualFile[] roots = AnnotationOrderRootType.getFiles(entry); roots = filterByReadOnliness(roots); if (roots.length > 0) { chooseRootAndAnnotateExternally(listOwner, annotationFQName, fromFile, project, packageName, roots, value); } else { Application application = ApplicationManager.getApplication(); if (application.isUnitTestMode() || application.isHeadlessEnvironment()) { notifyAfterAnnotationChanging(listOwner, annotationFQName, false); return; } application.invokeLater(new Runnable() { @Override public void run() { setupRootAndAnnotateExternally(entry, project, listOwner, annotationFQName, fromFile, packageName, value); } }, project.getDisposed()); } break; } } @Nullable protected List findExternalAnnotationsXmlFiles(@NotNull PsiModifierListOwner listOwner) { List psiFiles = findExternalAnnotationsFiles(listOwner); if (psiFiles == null) { return null; } List xmlFiles = new ArrayList(); for (PsiFile psiFile : psiFiles) { if (psiFile instanceof XmlFile) { xmlFiles.add((XmlFile)psiFile); } } return xmlFiles; } private void setupRootAndAnnotateExternally(@NotNull final OrderEntry entry, @NotNull final Project project, @NotNull final PsiModifierListOwner listOwner, @NotNull final String annotationFQName, @NotNull final PsiFile fromFile, @NotNull final String packageName, @Nullable final PsiNameValuePair[] value) { final FileChooserDescriptor descriptor = FileChooserDescriptorFactory.createSingleFolderDescriptor(); descriptor.setTitle(ProjectBundle.message("external.annotations.root.chooser.title", entry.getPresentableName())); descriptor.setDescription(ProjectBundle.message("external.annotations.root.chooser.description")); final VirtualFile newRoot = FileChooser.chooseFile(descriptor, project, null); if (newRoot == null) { notifyAfterAnnotationChanging(listOwner, annotationFQName, false); return; } new WriteCommandAction(project) { @Override protected void run(final Result result) throws Throwable { appendChosenAnnotationsRoot(entry, newRoot); XmlFile xmlFileInRoot = findXmlFileInRoot(findExternalAnnotationsXmlFiles(listOwner), newRoot); if (xmlFileInRoot != null) { //file already exists under appeared content root if (!FileModificationService.getInstance().preparePsiElementForWrite(xmlFileInRoot)) { notifyAfterAnnotationChanging(listOwner, annotationFQName, false); return; } annotateExternally(listOwner, annotationFQName, xmlFileInRoot, fromFile, value); } else { final XmlFile annotationsXml = createAnnotationsXml(newRoot, packageName); if (annotationsXml != null) { List createdFiles = new SmartList(annotationsXml); cacheExternalAnnotations(packageName, fromFile, createdFiles); } annotateExternally(listOwner, annotationFQName, annotationsXml, fromFile, value); } } }.execute(); } @Nullable private static XmlFile findXmlFileInRoot(@Nullable List xmlFiles, @NotNull VirtualFile root) { if (xmlFiles != null) { for (XmlFile xmlFile : xmlFiles) { VirtualFile vf = xmlFile.getVirtualFile(); if (vf != null) { if (VfsUtilCore.isAncestor(root, vf, false)) { return xmlFile; } } } } return null; } private void chooseRootAndAnnotateExternally(@NotNull final PsiModifierListOwner listOwner, @NotNull final String annotationFQName, @NotNull final PsiFile fromFile, @NotNull final Project project, @NotNull final String packageName, @NotNull VirtualFile[] roots, @Nullable final PsiNameValuePair[] value) { if (roots.length > 1) { JBPopupFactory.getInstance().createListPopup(new BaseListPopupStep("Annotation Roots", roots) { @Override public void canceled() { notifyAfterAnnotationChanging(listOwner, annotationFQName, false); } @Override public PopupStep onChosen(@NotNull final VirtualFile file, final boolean finalChoice) { annotateExternally(file, listOwner, project, packageName, annotationFQName, fromFile, value); return FINAL_CHOICE; } @NotNull @Override public String getTextFor(@NotNull final VirtualFile value) { return value.getPresentableUrl(); } @Override public Icon getIconFor(final VirtualFile aValue) { return AllIcons.Modules.Annotation; } }).showInBestPositionFor(DataManager.getInstance().getDataContext()); } else { annotateExternally(roots[0], listOwner, project, packageName, annotationFQName, fromFile, value); } } @NotNull private static VirtualFile[] filterByReadOnliness(@NotNull VirtualFile[] files) { List result = ContainerUtil.filter(files, new Condition() { @Override public boolean value(VirtualFile file) { return file.isInLocalFileSystem(); } }); return VfsUtilCore.toVirtualFileArray(result); } private void annotateExternally(@NotNull final VirtualFile root, @NotNull final PsiModifierListOwner listOwner, @NotNull final Project project, @NotNull final String packageName, @NotNull final String annotationFQName, @NotNull final PsiFile fromFile, @Nullable final PsiNameValuePair[] value) { List xmlFiles = findExternalAnnotationsXmlFiles(listOwner); final XmlFile existingXml = findXmlFileInRoot(xmlFiles, root); if (existingXml != null && !FileModificationService.getInstance().preparePsiElementForWrite(existingXml)) { notifyAfterAnnotationChanging(listOwner, annotationFQName, false); return; } final Set annotationFiles = xmlFiles == null ? new THashSet() : new THashSet(xmlFiles); new WriteCommandAction(project) { @Override protected void run(final Result result) throws Throwable { if (existingXml != null) { annotateExternally(listOwner, annotationFQName, existingXml, fromFile, value); } else { XmlFile newXml = createAnnotationsXml(root, packageName); if (newXml == null) { notifyAfterAnnotationChanging(listOwner, annotationFQName, false); } else { annotationFiles.add(newXml); cacheExternalAnnotations(packageName, fromFile, new SmartList(annotationFiles)); annotateExternally(listOwner, annotationFQName, newXml, fromFile, value); } } UndoManager.getInstance(project).undoableActionPerformed(new BasicUndoableAction() { @Override public void undo() { dropCache(); notifyChangedExternally(); } @Override public void redo() { dropCache(); notifyChangedExternally(); } }); } }.execute(); } @Override public boolean deannotate(@NotNull final PsiModifierListOwner listOwner, @NotNull final String annotationFQN) { ApplicationManager.getApplication().assertIsDispatchThread(); return processExistingExternalAnnotations(listOwner, annotationFQN, new Processor() { @Override public boolean process(XmlTag annotationTag) { PsiElement parent = annotationTag.getParent(); annotationTag.delete(); if (parent instanceof XmlTag) { if (((XmlTag)parent).getSubTags().length == 0) { parent.delete(); } } return true; } }); } @Override public boolean editExternalAnnotation(@NotNull PsiModifierListOwner listOwner, @NotNull final String annotationFQN, @Nullable final PsiNameValuePair[] value) { ApplicationManager.getApplication().assertIsDispatchThread(); return processExistingExternalAnnotations(listOwner, annotationFQN, new Processor() { @Override public boolean process(XmlTag annotationTag) { annotationTag.replace(XmlElementFactory.getInstance(myPsiManager.getProject()).createTagFromText( createAnnotationTag(annotationFQN, value))); return true; } }); } private boolean processExistingExternalAnnotations(@NotNull final PsiModifierListOwner listOwner, @NotNull final String annotationFQN, @NotNull final Processor annotationTagProcessor) { try { final List files = findExternalAnnotationsXmlFiles(listOwner); if (files == null) { notifyAfterAnnotationChanging(listOwner, annotationFQN, false); return false; } boolean processedAnything = false; for (final XmlFile file : files) { if (!file.isValid()) { continue; } if (ReadonlyStatusHandler.getInstance(myPsiManager.getProject()) .ensureFilesWritable(file.getVirtualFile()).hasReadonlyFiles()) { continue; } final XmlDocument document = file.getDocument(); if (document == null) { continue; } final XmlTag rootTag = document.getRootTag(); if (rootTag == null) { continue; } final String externalName = getExternalName(listOwner, false); final List tagsToProcess = new ArrayList(); for (XmlTag tag : rootTag.getSubTags()) { String className = StringUtil.unescapeXml(tag.getAttributeValue("name")); if (!Comparing.strEqual(className, externalName)) { continue; } for (XmlTag annotationTag : tag.getSubTags()) { if (!Comparing.strEqual(annotationTag.getAttributeValue("name"), annotationFQN)) { continue; } tagsToProcess.add(annotationTag); processedAnything = true; } } if (tagsToProcess.isEmpty()) { continue; } CommandProcessor.getInstance().executeCommand(myPsiManager.getProject(), new Runnable() { @Override public void run() { PsiDocumentManager.getInstance(myPsiManager.getProject()).commitAllDocuments(); try { for (XmlTag annotationTag : tagsToProcess) { annotationTagProcessor.process(annotationTag); } commitChanges(file); } catch (IncorrectOperationException e) { LOG.error(e); } } }, ExternalAnnotationsManagerImpl.class.getName(), null); } notifyAfterAnnotationChanging(listOwner, annotationFQN, processedAnything); return processedAnything; } finally { dropCache(); } } @Override @NotNull public AnnotationPlace chooseAnnotationsPlace(@NotNull final PsiElement element) { ApplicationManager.getApplication().assertIsDispatchThread(); if (!element.isPhysical()) return AnnotationPlace.IN_CODE; //element just created if (!element.getManager().isInProject(element)) return AnnotationPlace.EXTERNAL; final Project project = myPsiManager.getProject(); final PsiFile containingFile = element.getContainingFile(); final VirtualFile virtualFile = containingFile.getVirtualFile(); LOG.assertTrue(virtualFile != null); final List entries = ProjectRootManager.getInstance(project).getFileIndex().getOrderEntriesForFile(virtualFile); if (!entries.isEmpty()) { for (OrderEntry entry : entries) { if (!(entry instanceof ModuleOrderEntry)) { if (AnnotationOrderRootType.getUrls(entry).length > 0) { return AnnotationPlace.EXTERNAL; } break; } } } final MyExternalPromptDialog dialog = ApplicationManager.getApplication().isUnitTestMode() || ApplicationManager.getApplication().isHeadlessEnvironment() ? null : new MyExternalPromptDialog(project); if (dialog != null && dialog.isToBeShown()) { final PsiElement highlightElement = element instanceof PsiNameIdentifierOwner ? ((PsiNameIdentifierOwner)element).getNameIdentifier() : element.getNavigationElement(); LOG.assertTrue(highlightElement != null); final Editor editor = FileEditorManager.getInstance(project).getSelectedTextEditor(); final List highlighters = new ArrayList(); final boolean highlight = editor != null && editor.getDocument() == PsiDocumentManager.getInstance(project).getDocument(containingFile); try { if (highlight) { //do not highlight for batch inspections final EditorColorsManager colorsManager = EditorColorsManager.getInstance(); final TextAttributes attributes = colorsManager.getGlobalScheme().getAttributes(EditorColors.SEARCH_RESULT_ATTRIBUTES); final TextRange textRange = highlightElement.getTextRange(); HighlightManager.getInstance(project).addRangeHighlight(editor, textRange.getStartOffset(), textRange.getEndOffset(), attributes, true, highlighters); final LogicalPosition logicalPosition = editor.offsetToLogicalPosition(textRange.getStartOffset()); editor.getScrollingModel().scrollTo(logicalPosition, ScrollType.CENTER); } dialog.show(); if (dialog.getExitCode() == 2) { return AnnotationPlace.EXTERNAL; } else if (dialog.getExitCode() == 1) { return AnnotationPlace.NOWHERE; } } finally { if (highlight) { HighlightManager.getInstance(project).removeSegmentHighlighter(editor, highlighters.get(0)); } } } else if (dialog != null) { dialog.close(DialogWrapper.OK_EXIT_CODE); } return AnnotationPlace.IN_CODE; } private void appendChosenAnnotationsRoot(@NotNull final OrderEntry entry, @NotNull final VirtualFile vFile) { if (entry instanceof LibraryOrderEntry) { Library library = ((LibraryOrderEntry)entry).getLibrary(); LOG.assertTrue(library != null); final Library.ModifiableModel model = library.getModifiableModel(); model.addRoot(vFile, AnnotationOrderRootType.getInstance()); model.commit(); } else if (entry instanceof ModuleSourceOrderEntry) { final ModifiableRootModel model = ModuleRootManager.getInstance(entry.getOwnerModule()).getModifiableModel(); final JavaModuleExternalPaths extension = model.getModuleExtension(JavaModuleExternalPaths.class); extension.setExternalAnnotationUrls(ArrayUtil.mergeArrays(extension.getExternalAnnotationsUrls(), vFile.getUrl())); model.commit(); } else if (entry instanceof JdkOrderEntry) { final SdkModificator sdkModificator = ((JdkOrderEntry)entry).getJdk().getSdkModificator(); sdkModificator.addRoot(vFile, AnnotationOrderRootType.getInstance()); sdkModificator.commitChanges(); } dropCache(); } private void annotateExternally(@NotNull final PsiModifierListOwner listOwner, @NotNull final String annotationFQName, @Nullable final XmlFile xmlFile, @NotNull final PsiFile codeUsageFile, @Nullable final PsiNameValuePair[] values) { if (xmlFile == null) { notifyAfterAnnotationChanging(listOwner, annotationFQName, false); return; } CommandProcessor.getInstance().executeCommand(myPsiManager.getProject(), new Runnable() { @Override public void run() { try { final XmlDocument document = xmlFile.getDocument(); if (document != null) { final XmlTag rootTag = document.getRootTag(); final String externalName = getExternalName(listOwner, false); if (rootTag != null) { XmlTag anchor = null; for (XmlTag item : rootTag.getSubTags()) { int compare = Comparing.compare(externalName, StringUtil.unescapeXml(item.getAttributeValue("name"))); if (compare == 0) { anchor = null; for (XmlTag annotation : item.getSubTags()) { compare = Comparing.compare(annotationFQName, annotation.getAttributeValue("name")); if (compare == 0) { annotation.delete(); break; } anchor = annotation; } XmlTag newTag = XmlElementFactory.getInstance(myPsiManager.getProject()).createTagFromText( createAnnotationTag(annotationFQName, values)); item.addAfter(newTag, anchor); commitChanges(xmlFile); notifyAfterAnnotationChanging(listOwner, annotationFQName, true); return; } if (compare < 0) break; anchor = item; } @NonNls String text = "\n"; text += createAnnotationTag(annotationFQName, values); text += ""; rootTag.addAfter(XmlElementFactory.getInstance(myPsiManager.getProject()).createTagFromText(text), anchor); } } commitChanges(xmlFile); notifyAfterAnnotationChanging(listOwner, annotationFQName, true); } catch (IncorrectOperationException e) { LOG.error(e); notifyAfterAnnotationChanging(listOwner, annotationFQName, false); } finally { dropCache(); if (codeUsageFile.getVirtualFile().isInLocalFileSystem()) { UndoUtil.markPsiFileForUndo(codeUsageFile); } } } }, ExternalAnnotationsManagerImpl.class.getName(), null); } private static void sortItems(@NotNull XmlFile xmlFile) { XmlDocument document = xmlFile.getDocument(); if (document == null) { return; } XmlTag rootTag = document.getRootTag(); if (rootTag == null) { return; } List itemTags = new ArrayList(); for (XmlTag item : rootTag.getSubTags()) { if (item.getAttributeValue("name") != null) { itemTags.add(item); } else { item.delete(); } } List sorted = new ArrayList(itemTags); Collections.sort(sorted, new Comparator() { @Override public int compare(XmlTag item1, XmlTag item2) { String externalName1 = item1.getAttributeValue("name"); String externalName2 = item2.getAttributeValue("name"); assert externalName1 != null && externalName2 != null; // null names were not added return externalName1.compareTo(externalName2); } }); if (!sorted.equals(itemTags)) { for (XmlTag item : sorted) { rootTag.addAfter(item, null); item.delete(); } } } private void commitChanges(XmlFile xmlFile) { sortItems(xmlFile); Document doc = PsiDocumentManager.getInstance(myPsiManager.getProject()).getDocument(xmlFile); assert doc != null; FileDocumentManager.getInstance().saveDocument(doc); } @NonNls @NotNull private static String createAnnotationTag(@NotNull String annotationFQName, @Nullable PsiNameValuePair[] values) { @NonNls String text; if (values != null && values.length != 0) { text = " \n"; text += StringUtil.join(values, new Function() { @NonNls @NotNull @Override public String fun(@NotNull PsiNameValuePair pair) { return ""; } }, " \n"); text += " "; } else { text = " \n"; } return text; } @Nullable private XmlFile createAnnotationsXml(@NotNull VirtualFile root, @NonNls @NotNull String packageName) { final String[] dirs = packageName.split("[\\.]"); for (String dir : dirs) { if (dir.isEmpty()) break; VirtualFile subdir = root.findChild(dir); if (subdir == null) { try { subdir = root.createChildDirectory(null, dir); } catch (IOException e) { LOG.error(e); } } root = subdir; } final PsiDirectory directory = myPsiManager.findDirectory(root); if (directory == null) return null; final PsiFile psiFile = directory.findFile(ANNOTATIONS_XML); if (psiFile instanceof XmlFile) { return (XmlFile)psiFile; } try { final PsiFileFactory factory = PsiFileFactory.getInstance(myPsiManager.getProject()); return (XmlFile)directory.add(factory.createFileFromText(ANNOTATIONS_XML, XmlFileType.INSTANCE, "")); } catch (IncorrectOperationException e) { LOG.error(e); } return null; } @Override protected void duplicateError(@NotNull PsiFile file, @NotNull String externalName, @NotNull String text) { String message = text + "; for signature: '" + externalName + "' in the file " + file.getVirtualFile().getPresentableUrl(); LogMessageEx.error(LOG, message, file.getText()); } private static class MyExternalPromptDialog extends OptionsMessageDialog { private final Project myProject; private static final String ADD_IN_CODE = ProjectBundle.message("external.annotations.in.code.option"); private static final String MESSAGE = ProjectBundle.message("external.annotations.suggestion.message"); public MyExternalPromptDialog(final Project project) { super(project, MESSAGE, ProjectBundle.message("external.annotation.prompt"), Messages.getQuestionIcon()); myProject = project; init(); } @Override protected String getOkActionName() { return ADD_IN_CODE; } @Override @NotNull protected String getCancelActionName() { return CommonBundle.getCancelButtonText(); } @Override @NotNull @SuppressWarnings({"NonStaticInitializer"}) protected Action[] createActions() { final Action okAction = getOKAction(); assignMnemonic(ADD_IN_CODE, okAction); final String externalName = ProjectBundle.message("external.annotations.external.option"); return new Action[]{okAction, new AbstractAction(externalName) { { assignMnemonic(externalName, this); } @Override public void actionPerformed(final ActionEvent e) { if (canBeHidden()) { setToBeShown(toBeShown(), true); } close(2); } }, getCancelAction()}; } @Override protected boolean isToBeShown() { return CodeStyleSettingsManager.getSettings(myProject).USE_EXTERNAL_ANNOTATIONS; } @Override protected void setToBeShown(boolean value, boolean onOk) { CodeStyleSettingsManager.getSettings(myProject).USE_EXTERNAL_ANNOTATIONS = value; } @Override protected JComponent createNorthPanel() { final JPanel northPanel = (JPanel)super.createNorthPanel(); northPanel.add(new JLabel(MESSAGE), BorderLayout.CENTER); return northPanel; } @Override protected boolean shouldSaveOptionsOnCancel() { return true; } } private class MyVirtualFileListener extends VirtualFileAdapter { private void processEvent(VirtualFileEvent event) { if (event.isFromRefresh() && ANNOTATIONS_XML.equals(event.getFileName())) { dropCache(); notifyChangedExternally(); } } @Override public void contentsChanged(@NotNull VirtualFileEvent event) { processEvent(event); } @Override public void fileCreated(@NotNull VirtualFileEvent event) { processEvent(event); } @Override public void fileDeleted(@NotNull VirtualFileEvent event) { processEvent(event); } @Override public void fileMoved(@NotNull VirtualFileMoveEvent event) { processEvent(event); } @Override public void fileCopied(@NotNull VirtualFileCopyEvent event) { processEvent(event); } } }