com.intellij.codeInsight.ExternalAnnotationsManagerImpl.java Source code

Java tutorial

Introduction

Here is the source code for com.intellij.codeInsight.ExternalAnnotationsManagerImpl.java

Source

/*
 * Copyright 2000-2012 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.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.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.ArrayList;
import java.util.List;
import java.util.Set;

/**
 * @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);
    }

    private void notifyChangedExternally() {
        myBus.syncPublisher(TOPIC).externalAnnotationsChangedExternally();
    }

    @Override
    public void annotateExternally(@NotNull final PsiModifierListOwner listOwner,
            @NotNull final String annotationFQName, @NotNull final PsiFile fromFile,
            @Nullable final PsiNameValuePair[] value) {
        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<OrderEntry> 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 = entry.getFiles(AnnotationOrderRootType.getInstance());
            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<XmlFile> findExternalAnnotationsXmlFiles(@NotNull PsiModifierListOwner listOwner) {
        List<PsiFile> psiFiles = findExternalAnnotationsFiles(listOwner);
        if (psiFiles == null) {
            return null;
        }
        List<XmlFile> xmlFiles = new ArrayList<XmlFile>();
        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<PsiFile> createdFiles = new SmartList<PsiFile>(annotationsXml);
                        cacheExternalAnnotations(packageName, fromFile, createdFiles);
                    }
                    annotateExternally(listOwner, annotationFQName, annotationsXml, fromFile, value);
                }
            }
        }.execute();
    }

    @Nullable
    private static XmlFile findXmlFileInRoot(@Nullable List<XmlFile> 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<VirtualFile>("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<VirtualFile> result = ContainerUtil.filter(files, new Condition<VirtualFile>() {
            @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<XmlFile> xmlFiles = findExternalAnnotationsXmlFiles(listOwner);

        final XmlFile existingXml = findXmlFileInRoot(xmlFiles, root);
        if (existingXml != null && !FileModificationService.getInstance().preparePsiElementForWrite(existingXml)) {
            notifyAfterAnnotationChanging(listOwner, annotationFQName, false);
            return;
        }

        final Set<PsiFile> annotationFiles = xmlFiles == null ? new THashSet<PsiFile>()
                : new THashSet<PsiFile>(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<PsiFile>(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) {
        return processExistingExternalAnnotations(listOwner, annotationFQN, new Processor<XmlTag>() {
            @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) {
        return processExistingExternalAnnotations(listOwner, annotationFQN, new Processor<XmlTag>() {
            @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<XmlTag> annotationTagProcessor) {
        try {
            final List<XmlFile> 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 String oldExternalName = getNormalizedExternalName(listOwner);
                for (final XmlTag tag : rootTag.getSubTags()) {
                    final String className = StringUtil.unescapeXml(tag.getAttributeValue("name"));
                    if (!Comparing.strEqual(className, externalName)
                            && !Comparing.strEqual(className, oldExternalName)) {
                        continue;
                    }
                    for (final XmlTag annotationTag : tag.getSubTags()) {
                        if (!Comparing.strEqual(annotationTag.getAttributeValue("name"), annotationFQN)) {
                            continue;
                        }
                        CommandProcessor.getInstance().executeCommand(myPsiManager.getProject(), new Runnable() {
                            @Override
                            public void run() {
                                try {
                                    annotationTagProcessor.process(annotationTag);
                                    commitChanges(file);
                                } catch (IncorrectOperationException e) {
                                    LOG.error(e);
                                }
                            }
                        }, ExternalAnnotationsManagerImpl.class.getName(), null);
                        processedAnything = true;
                    }
                }
            }
            notifyAfterAnnotationChanging(listOwner, annotationFQN, processedAnything);
            return processedAnything;
        } finally {
            dropCache();
        }
    }

    @Override
    @NotNull
    public AnnotationPlace chooseAnnotationsPlace(@NotNull final PsiElement element) {
        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<OrderEntry> entries = ProjectRootManager.getInstance(project).getFileIndex()
                .getOrderEntriesForFile(virtualFile);
        if (!entries.isEmpty()) {
            for (OrderEntry entry : entries) {
                if (!(entry instanceof ModuleOrderEntry)) {
                    if (entry.getFiles(AnnotationOrderRootType.getInstance()).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<RangeHighlighter> highlighters = new ArrayList<RangeHighlighter>();
            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 ModifiableRootModel rootModel = ModuleRootManager.getInstance(entry.getOwnerModule())
                    .getModifiableModel();
            final Library.ModifiableModel model = library.getModifiableModel();
            model.addRoot(vFile, AnnotationOrderRootType.getInstance());
            model.commit();
            rootModel.commit();
        } else if (entry instanceof SdkOrderEntry) {
            final SdkModificator sdkModificator = ((SdkOrderEntry) entry).getSdk().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) {
                            for (XmlTag item : rootTag.getSubTags()) {
                                if (Comparing.strEqual(StringUtil.unescapeXml(item.getAttributeValue("name")),
                                        externalName)) {
                                    for (XmlTag annotation : item.getSubTags()) {
                                        if (Comparing.strEqual(annotation.getAttributeValue("name"),
                                                annotationFQName)) {
                                            annotation.delete();
                                            break;
                                        }
                                    }
                                    XmlTag newTag = XmlElementFactory.getInstance(myPsiManager.getProject())
                                            .createTagFromText(createAnnotationTag(annotationFQName, values));
                                    item.add(newTag);
                                    commitChanges(xmlFile);
                                    notifyAfterAnnotationChanging(listOwner, annotationFQName, true);
                                    return;
                                }
                            }
                            @NonNls
                            String text = "<item name=\'" + StringUtil.escapeXml(externalName) + "\'>\n";
                            text += createAnnotationTag(annotationFQName, values);
                            text += "</item>";
                            rootTag.add(XmlElementFactory.getInstance(myPsiManager.getProject())
                                    .createTagFromText(text));
                        }
                    }
                    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 void commitChanges(XmlFile 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 = "  <annotation name=\'" + annotationFQName + "\'>\n";
            text += StringUtil.join(values, new Function<PsiNameValuePair, String>() {
                @NonNls
                @NotNull
                @Override
                public String fun(@NotNull PsiNameValuePair pair) {
                    return "<val" + (pair.getName() != null ? " name=\"" + pair.getName() + "\"" : "") + " val=\""
                            + StringUtil.escapeXml(pair.getValue().getText()) + "\"/>";
                }
            }, "    \n");
            text += "  </annotation>";
        } else {
            text = "  <annotation name=\'" + annotationFQName + "\'/>\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, "<root></root>"));
        } catch (IncorrectOperationException e) {
            LOG.error(e);
        }
        return null;
    }

    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(VirtualFileEvent event) {
            processEvent(event);
        }

        @Override
        public void fileCreated(VirtualFileEvent event) {
            processEvent(event);
        }

        @Override
        public void fileDeleted(VirtualFileEvent event) {
            processEvent(event);
        }

        @Override
        public void fileMoved(VirtualFileMoveEvent event) {
            processEvent(event);
        }

        @Override
        public void fileCopied(VirtualFileCopyEvent event) {
            processEvent(event);
        }
    }
}