Merge branch 'ypankratyev/duplicate_extension_in_register_quickfix'
[idea/community.git] / plugins / devkit / src / inspections / quickfix / RegisterExtensionFix.java
1 /*
2  * Copyright 2000-2016 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package org.jetbrains.idea.devkit.inspections.quickfix;
17
18 import com.intellij.codeInsight.intention.IntentionAction;
19 import com.intellij.lang.LanguageExtensionPoint;
20 import com.intellij.openapi.application.Result;
21 import com.intellij.openapi.command.WriteCommandAction;
22 import com.intellij.openapi.editor.Editor;
23 import com.intellij.openapi.extensions.KeyedFactoryEPBean;
24 import com.intellij.openapi.fileTypes.FileTypeExtensionPoint;
25 import com.intellij.openapi.project.DumbService;
26 import com.intellij.openapi.project.Project;
27 import com.intellij.openapi.ui.popup.JBPopupFactory;
28 import com.intellij.openapi.ui.popup.PopupStep;
29 import com.intellij.openapi.ui.popup.util.BaseListPopupStep;
30 import com.intellij.openapi.util.ClassExtensionPoint;
31 import com.intellij.psi.PsiClass;
32 import com.intellij.psi.PsiElement;
33 import com.intellij.psi.PsiFile;
34 import com.intellij.psi.xml.XmlAttribute;
35 import com.intellij.psi.xml.XmlTag;
36 import com.intellij.util.IncorrectOperationException;
37 import com.intellij.util.KeyedLazyInstanceEP;
38 import com.intellij.util.PsiNavigateUtil;
39 import com.intellij.util.containers.ContainerUtil;
40 import com.intellij.util.xml.DomFileElement;
41 import org.jetbrains.annotations.NotNull;
42 import org.jetbrains.idea.devkit.dom.Extension;
43 import org.jetbrains.idea.devkit.dom.Extensions;
44 import org.jetbrains.idea.devkit.dom.IdeaPlugin;
45 import org.jetbrains.idea.devkit.util.ExtensionPointCandidate;
46
47 import java.util.ArrayList;
48 import java.util.Map;
49 import java.util.Set;
50
51 public class RegisterExtensionFix implements IntentionAction {
52   private final PsiClass myExtensionClass;
53   private final Set<ExtensionPointCandidate> myEPCandidates;
54
55   public RegisterExtensionFix(PsiClass extensionClass, Set<ExtensionPointCandidate> epCandidates) {
56     myExtensionClass = extensionClass;
57     myEPCandidates = epCandidates;
58   }
59
60   @NotNull
61   @Override
62   public String getText() {
63     return "Register extension";
64   }
65
66   @NotNull
67   @Override
68   public String getFamilyName() {
69     return getText();
70   }
71
72   @Override
73   public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) {
74     return !DumbService.isDumb(project);
75   }
76
77   @Override
78   public void invoke(@NotNull Project project, final Editor editor, PsiFile file) throws IncorrectOperationException {
79     PluginDescriptorChooser.show(project, editor, file, element -> doFix(editor, element));
80   }
81
82   private void doFix(Editor editor, final DomFileElement<IdeaPlugin> element) {
83     if (myEPCandidates.size() == 1) {
84       registerExtension(element, myEPCandidates.iterator().next());
85     }
86     else {
87       final BaseListPopupStep<ExtensionPointCandidate> popupStep =
88         new BaseListPopupStep<ExtensionPointCandidate>("Choose Extension Point", new ArrayList<>(myEPCandidates)) {
89           @Override
90           public PopupStep onChosen(ExtensionPointCandidate selectedValue, boolean finalChoice) {
91             registerExtension(element, selectedValue);
92             return FINAL_CHOICE;
93           }
94         };
95       JBPopupFactory.getInstance().createListPopup(popupStep).showInBestPositionFor(editor);
96     }
97   }
98
99   private void registerExtension(final DomFileElement<IdeaPlugin> selectedValue, final ExtensionPointCandidate candidate) {
100     PsiElement navTarget = new WriteCommandAction<PsiElement>(selectedValue.getFile().getProject(), selectedValue.getFile()) {
101       @Override
102       protected void run(@NotNull Result<PsiElement> result) throws Throwable {
103         Extensions extensions = PluginDescriptorChooser.findOrCreateExtensionsForEP(selectedValue, candidate.epName);
104         Extension extension = extensions.addExtension(candidate.epName);
105         XmlTag tag = extension.getXmlTag();
106         PsiElement navTarget = null;
107         String keyAttrName = KEY_MAP.get(candidate.beanClassName);
108         if (keyAttrName != null) {
109           XmlAttribute attr = tag.setAttribute(keyAttrName, "");
110           navTarget = attr.getValueElement();
111         }
112         if (candidate.attributeName != null) {
113           tag.setAttribute(candidate.attributeName, myExtensionClass.getQualifiedName());
114         }
115         else {
116           XmlTag subTag = tag.createChildTag(candidate.tagName, null, myExtensionClass.getQualifiedName(), false);
117           tag.addSubTag(subTag, false);
118         }
119         result.setResult(navTarget != null ? navTarget : extension.getXmlTag());
120       }
121     }.execute().throwException().getResultObject();
122     PsiNavigateUtil.navigate(navTarget);
123   }
124
125   private static final Map<String, String> KEY_MAP = ContainerUtil.<String, String>immutableMapBuilder()
126     .put(KeyedFactoryEPBean.class.getName(), "key")
127     .put(KeyedLazyInstanceEP.class.getName(), "key")
128     .put(FileTypeExtensionPoint.class.getName(), "filetype")
129     .put(LanguageExtensionPoint.class.getName(), "language")
130     .put(ClassExtensionPoint.class.getName(), "forClass")
131     .build();
132
133
134   @Override
135   public boolean startInWriteAction() {
136     return false;
137   }
138 }