blob: af5f14e2513118f87ab9e52e90fbca739bfff2c2 [file] [log] [blame]
/*
* 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<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 = 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<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) {
ApplicationManager.getApplication().assertIsDispatchThread();
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) {
ApplicationManager.getApplication().assertIsDispatchThread();
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 List<XmlTag> tagsToProcess = new ArrayList<XmlTag>();
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<OrderEntry> 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<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 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 =
"<item name=\'" + StringUtil.escapeXml(externalName) + "\'>\n";
text += createAnnotationTag(annotationFQName, values);
text += "</item>";
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<XmlTag> itemTags = new ArrayList<XmlTag>();
for (XmlTag item : rootTag.getSubTags()) {
if (item.getAttributeValue("name") != null) {
itemTags.add(item);
}
else {
item.delete();
}
}
List<XmlTag> sorted = new ArrayList<XmlTag>(itemTags);
Collections.sort(sorted, new Comparator<XmlTag>() {
@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 = " <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;
}
@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);
}
}
}