mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-18 09:34:34 +07:00
786 lines
31 KiB
Java
786 lines
31 KiB
Java
/*
|
|
* 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.
|
|
*/
|
|
|
|
/*
|
|
* User: anna
|
|
* Date: 26-Jun-2007
|
|
*/
|
|
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.openapi.Disposable;
|
|
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.command.undo.UnexpectedUndoException;
|
|
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.module.Module;
|
|
import com.intellij.openapi.module.ModuleManager;
|
|
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.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.messages.MessageBus;
|
|
import com.intellij.util.messages.MessageBusConnection;
|
|
import com.intellij.util.ui.OptionsMessageDialog;
|
|
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;
|
|
|
|
public class ExternalAnnotationsManagerImpl extends BaseExternalAnnotationsManager {
|
|
private static final Logger LOG = Logger.getInstance("#" + ExternalAnnotationsManagerImpl.class.getName());
|
|
|
|
@NotNull private volatile ThreeState myHasAnyAnnotationsRoots = ThreeState.UNSURE;
|
|
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();
|
|
notifyChangedDramatically();
|
|
}
|
|
});
|
|
|
|
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 notifyChangedDramatically() {
|
|
myBus.syncPublisher(TOPIC).externalAnnotationsChangedDramatically();
|
|
}
|
|
|
|
@Override
|
|
protected void dropCache() {
|
|
super.dropCache();
|
|
myHasAnyAnnotationsRoots = ThreeState.UNSURE;
|
|
}
|
|
|
|
@Override
|
|
protected boolean hasAnyAnnotationsRoots() {
|
|
if (myHasAnyAnnotationsRoots == ThreeState.UNSURE) {
|
|
final Module[] modules = ModuleManager.getInstance(myPsiManager.getProject()).getModules();
|
|
for (Module module : modules) {
|
|
for (OrderEntry entry : ModuleRootManager.getInstance(module).getOrderEntries()) {
|
|
final String[] urls = AnnotationOrderRootType.getUrls(entry);
|
|
if (urls.length > 0) {
|
|
myHasAnyAnnotationsRoots = ThreeState.YES;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
myHasAnyAnnotationsRoots = ThreeState.NO;
|
|
}
|
|
return myHasAnyAnnotationsRoots == ThreeState.YES;
|
|
}
|
|
|
|
|
|
@Override
|
|
public void annotateExternally(@NotNull final PsiModifierListOwner listOwner,
|
|
@NotNull final String annotationFQName,
|
|
@NotNull final PsiFile fromFile,
|
|
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 = AnnotationOrderRootType.getFiles(entry);
|
|
roots = filterByReadOnliness(roots);
|
|
|
|
if (roots.length > 0) {
|
|
chooseRootAndAnnotateExternally(listOwner, annotationFQName, fromFile, project, packageName, roots, value);
|
|
}
|
|
else {
|
|
if (ApplicationManager.getApplication().isUnitTestMode() || ApplicationManager.getApplication().isHeadlessEnvironment()) {
|
|
notifyAfterAnnotationChanging(listOwner, annotationFQName, false);
|
|
return;
|
|
}
|
|
SwingUtilities.invokeLater(new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
setupRootAndAnnotateExternally(entry, project, listOwner, annotationFQName, fromFile, packageName, value);
|
|
}
|
|
});
|
|
}
|
|
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,
|
|
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 (!CodeInsightUtilBase.preparePsiElementForWrite(xmlFileInRoot)) {
|
|
notifyAfterAnnotationChanging(listOwner, annotationFQName, false);
|
|
return;
|
|
}
|
|
annotateExternally(listOwner, annotationFQName, xmlFileInRoot, fromFile, value);
|
|
}
|
|
else {
|
|
final XmlFile annotationsXml = createAnnotationsXml(newRoot, packageName);
|
|
if (annotationsXml != null) {
|
|
final List<PsiFile> createdFiles = new ArrayList<PsiFile>();
|
|
createdFiles.add(annotationsXml);
|
|
String fqn = getFQN(packageName, fromFile);
|
|
if (fqn != null) {
|
|
myExternalAnnotations.put(fqn, 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,
|
|
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 = new ArrayList<VirtualFile>();
|
|
for (VirtualFile file : files) {
|
|
if (file.isInLocalFileSystem()) {
|
|
result.add(file);
|
|
}
|
|
}
|
|
return VfsUtil.toVirtualFileArray(result);
|
|
}
|
|
|
|
private void annotateExternally(@NotNull final VirtualFile root,
|
|
@NotNull final PsiModifierListOwner listOwner,
|
|
@NotNull Project project,
|
|
@NotNull final String packageName,
|
|
@NotNull final String annotationFQName,
|
|
@NotNull final PsiFile fromFile,
|
|
final PsiNameValuePair[] value) {
|
|
List<XmlFile> xmlFiles = findExternalAnnotationsXmlFiles(listOwner);
|
|
|
|
final XmlFile existingXml = findXmlFileInRoot(xmlFiles, root);
|
|
if (existingXml != null && !CodeInsightUtilBase.preparePsiElementForWrite(existingXml)) {
|
|
notifyAfterAnnotationChanging(listOwner, annotationFQName, false);
|
|
return;
|
|
}
|
|
|
|
final List<PsiFile> annotationFiles = xmlFiles == null ? new ArrayList<PsiFile>() : new ArrayList<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);
|
|
myExternalAnnotations.put(getFQN(packageName, fromFile), annotationFiles);
|
|
annotateExternally(listOwner, annotationFQName, existingXml, fromFile, value);
|
|
}
|
|
}
|
|
}
|
|
}.execute();
|
|
|
|
UndoManager.getInstance(project).undoableActionPerformed(new BasicUndoableAction() {
|
|
@Override
|
|
public void undo() throws UnexpectedUndoException {
|
|
dropCache();
|
|
notifyChangedDramatically();
|
|
}
|
|
|
|
@Override
|
|
public void redo() throws UnexpectedUndoException {
|
|
dropCache();
|
|
notifyChangedDramatically();
|
|
}
|
|
});
|
|
}
|
|
|
|
@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,
|
|
@NotNull 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 = 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 (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 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 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();
|
|
}
|
|
myExternalAnnotations.clear();
|
|
}
|
|
|
|
private void annotateExternally(@NotNull final PsiModifierListOwner listOwner,
|
|
@NotNull final String annotationFQName,
|
|
@Nullable final XmlFile xmlFile,
|
|
@NotNull final PsiFile codeUsageFile,
|
|
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 tag : rootTag.getSubTags()) {
|
|
if (Comparing.strEqual(StringUtil.unescapeXml(tag.getAttributeValue("name")), externalName)) {
|
|
for (XmlTag annTag : tag.getSubTags()) {
|
|
if (Comparing.strEqual(annTag.getAttributeValue("name"), annotationFQName)) {
|
|
annTag.delete();
|
|
break;
|
|
}
|
|
}
|
|
tag.add(XmlElementFactory.getInstance(myPsiManager.getProject()).createTagFromText(
|
|
createAnnotationTag(annotationFQName, values)));
|
|
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 {
|
|
return (XmlFile)directory
|
|
.add(PsiFileFactory.getInstance(myPsiManager.getProject()).createFileFromText(ANNOTATIONS_XML, "<root></root>"));
|
|
}
|
|
catch (IncorrectOperationException e) {
|
|
LOG.error(e);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
@NotNull
|
|
protected List<VirtualFile> getExternalAnnotationsRoots(@NotNull VirtualFile libraryFile) {
|
|
final List<OrderEntry> entries = ProjectRootManager.getInstance(myPsiManager.getProject()).getFileIndex().getOrderEntriesForFile(
|
|
libraryFile);
|
|
List<VirtualFile> result = new ArrayList<VirtualFile>();
|
|
for (OrderEntry entry : entries) {
|
|
if (entry instanceof ModuleOrderEntry) {
|
|
continue;
|
|
}
|
|
final String[] externalUrls = AnnotationOrderRootType.getUrls(entry);
|
|
for (String url : externalUrls) {
|
|
VirtualFile root = VirtualFileManager.getInstance().findFileByUrl(url);
|
|
if (root != null) {
|
|
result.add(root);
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
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();
|
|
notifyChangedDramatically();
|
|
}
|
|
}
|
|
|
|
@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);
|
|
}
|
|
}
|
|
}
|