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