un-inject language action in Java fix
[fedora-idea.git] / plugins / IntelliLang / src / org / intellij / plugins / intelliLang / inject / InjectLanguageAction.java
blob16031d97b38f6c3b631cd2cc1581212c3c64773d
1 /*
2 * Copyright 2006 Sascha Weinreuter
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package org.intellij.plugins.intelliLang.inject;
18 import com.intellij.codeInsight.intention.IntentionAction;
19 import com.intellij.ide.DataManager;
20 import com.intellij.lang.Language;
21 import com.intellij.lang.StdLanguages;
22 import com.intellij.openapi.application.Result;
23 import com.intellij.openapi.command.WriteCommandAction;
24 import com.intellij.openapi.editor.Editor;
25 import com.intellij.openapi.fileTypes.FileType;
26 import com.intellij.openapi.options.Configurable;
27 import com.intellij.openapi.project.Project;
28 import com.intellij.openapi.ui.popup.JBPopupFactory;
29 import com.intellij.openapi.ui.popup.ListPopup;
30 import com.intellij.openapi.ui.popup.PopupStep;
31 import com.intellij.openapi.ui.popup.util.BaseListPopupStep;
32 import com.intellij.openapi.util.Comparing;
33 import com.intellij.openapi.util.Pair;
34 import com.intellij.openapi.util.TextRange;
35 import com.intellij.openapi.vfs.VirtualFile;
36 import com.intellij.psi.*;
37 import com.intellij.psi.codeStyle.JavaCodeStyleManager;
38 import com.intellij.psi.util.PsiTreeUtil;
39 import com.intellij.psi.util.PsiUtil;
40 import com.intellij.psi.xml.XmlAttribute;
41 import com.intellij.psi.xml.XmlAttributeValue;
42 import com.intellij.psi.xml.XmlTag;
43 import com.intellij.psi.xml.XmlText;
44 import com.intellij.util.FileContentUtil;
45 import com.intellij.util.IncorrectOperationException;
46 import com.intellij.util.Processor;
47 import com.intellij.util.ui.EmptyIcon;
48 import org.intellij.plugins.intelliLang.Configuration;
49 import org.intellij.plugins.intelliLang.inject.config.MethodParameterInjection;
50 import org.intellij.plugins.intelliLang.inject.config.XmlAttributeInjection;
51 import org.intellij.plugins.intelliLang.inject.config.XmlTagInjection;
52 import org.intellij.plugins.intelliLang.inject.config.ui.configurables.MethodParameterInjectionConfigurable;
53 import org.intellij.plugins.intelliLang.inject.config.ui.configurables.XmlAttributeInjectionConfigurable;
54 import org.intellij.plugins.intelliLang.inject.config.ui.configurables.XmlTagInjectionConfigurable;
55 import org.jetbrains.annotations.NotNull;
56 import org.jetbrains.annotations.Nullable;
58 import javax.swing.*;
59 import java.util.*;
61 public class InjectLanguageAction implements IntentionAction {
62 @NotNull
63 public String getText() {
64 return "Inject Language";
67 @NotNull
68 public String getFamilyName() {
69 return "Inject Language";
72 public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) {
73 final PsiLanguageInjectionHost host = findInjectionHost(editor, file);
74 if (host == null) {
75 return false;
77 final List<Pair<PsiElement, TextRange>> injectedPsi = host.getInjectedPsi();
78 if (injectedPsi == null || injectedPsi.isEmpty()) {
79 return true;
81 return false;
84 @Nullable
85 protected static PsiLanguageInjectionHost findInjectionHost(Editor editor, PsiFile file) {
86 final int offset = editor.getCaretModel().getOffset();
87 final PsiLanguageInjectionHost host =
88 PsiTreeUtil.getParentOfType(file.findElementAt(offset), PsiLanguageInjectionHost.class, false, true);
89 if (host == null) {
90 return null;
92 if (host instanceof PsiLiteralExpression) {
93 final PsiType type = ((PsiLiteralExpression)host).getType();
94 return type == null || !type.equalsToText("java.lang.String") ? null : host;
96 else if (host instanceof XmlAttributeValue) {
97 final PsiElement p = host.getParent();
98 if (p instanceof XmlAttribute) {
99 final String s = ((XmlAttribute)p).getName();
100 return s.equals("xmlns") || s.startsWith("xmlns:") ? null : host;
103 else if (host instanceof XmlText) {
104 final XmlTag tag = ((XmlText)host).getParentTag();
105 return tag == null || tag.getValue().getTextElements().length > 1 || tag.getSubTags().length > 0 ? null : host;
107 // unknown injection host
108 return null;
111 public void invoke(@NotNull final Project project, final Editor editor, final PsiFile file) throws IncorrectOperationException {
112 final PsiLanguageInjectionHost host = findInjectionHost(editor, file);
113 assert host != null;
114 doChooseLanguageToInject(new Processor<String>() {
115 public boolean process(final String languageId) {
116 if (!(host instanceof XmlAttributeValue && doInjectInAttributeValue(project, (XmlAttributeValue)host, languageId) ||
117 host instanceof XmlText && doInjectInXmlText(project, (XmlText)host, languageId) ||
118 host.getLanguage() == StdLanguages.JAVA && doInjectInJava(project, host, languageId))) {
119 CustomLanguageInjector.getInstance(project).addTempInjection(host, InjectedLanguage.create(languageId));
121 FileContentUtil.reparseFiles(project, Collections.<VirtualFile>emptyList(), true);
122 return false;
127 private static boolean doInjectInJava(final Project project, final PsiElement host, final String languageId) {
128 PsiElement target = host;
129 PsiElement parent = target.getParent();
130 for (; parent != null; target = parent, parent = target.getParent()) {
131 if (parent instanceof PsiBinaryExpression) continue;
132 if (parent instanceof PsiParenthesizedExpression) continue;
133 if (parent instanceof PsiConditionalExpression && ((PsiConditionalExpression)parent).getCondition() != target) continue;
134 break;
136 if (parent instanceof PsiReturnStatement ||
137 parent instanceof PsiMethod ||
138 parent instanceof PsiNameValuePair) {
139 return doInjectInJavaMethod(project, findPsiMethod(parent), -1, languageId);
141 else if (parent instanceof PsiExpressionList && parent.getParent() instanceof PsiMethodCallExpression) {
142 return doInjectInJavaMethod(project, findPsiMethod(parent), findParameterIndex(target, (PsiExpressionList)parent), languageId);
144 else if (parent instanceof PsiAssignmentExpression) {
145 final PsiExpression psiExpression = ((PsiAssignmentExpression)parent).getLExpression();
146 if (psiExpression instanceof PsiReferenceExpression) {
147 final PsiElement element = ((PsiReferenceExpression)psiExpression).resolve();
148 if (element != null) {
149 return doInjectInJava(project, element, languageId);
153 else if (parent instanceof PsiVariable) {
154 if (doAddLanguageAnnotation(project, (PsiModifierListOwner)parent, languageId)) return true;
156 return false;
159 private static boolean doAddLanguageAnnotation(final Project project, final PsiModifierListOwner modifierListOwner,
160 final String languageId) {
161 if (modifierListOwner.getModifierList() == null || !PsiUtil.getLanguageLevel(modifierListOwner).hasEnumKeywordAndAutoboxing()) return false;
162 new WriteCommandAction(project, modifierListOwner.getContainingFile()) {
163 protected void run(final Result result) throws Throwable {
164 final String annotationName = org.intellij.lang.annotations.Language.class.getName();
165 final PsiAnnotation annotation = JavaPsiFacade.getInstance(project).getElementFactory()
166 .createAnnotationFromText("@" + annotationName + "(\"" + languageId + "\")", modifierListOwner);
167 final PsiModifierList list = modifierListOwner.getModifierList();
168 assert list != null;
169 final PsiAnnotation existingAnnotation = list.findAnnotation(annotationName);
170 if (existingAnnotation != null) existingAnnotation.replace(annotation);
171 else list.addAfter(annotation, null);
172 JavaCodeStyleManager.getInstance(getProject()).shortenClassReferences(list);
174 }.execute();
175 return true;
178 private static boolean doInjectInJavaMethod(final Project project, final PsiMethod psiMethod, final int parameterIndex,
179 final String languageId) {
180 if (psiMethod == null) return false;
181 if (parameterIndex < -1) return false;
182 if (parameterIndex >= psiMethod.getParameterList().getParametersCount()) return false;
183 final PsiModifierList methodModifiers = psiMethod.getModifierList();
184 if (methodModifiers.hasModifierProperty(PsiModifier.PRIVATE) || methodModifiers.hasModifierProperty(PsiModifier.PACKAGE_LOCAL)) {
185 return doAddLanguageAnnotation(project, parameterIndex >0? psiMethod.getParameterList().getParameters()[parameterIndex - 1] : psiMethod, languageId);
187 final PsiClass containingClass = psiMethod.getContainingClass();
188 assert containingClass != null;
189 final PsiModifierList classModifiers = containingClass.getModifierList();
190 if (classModifiers != null && (classModifiers.hasModifierProperty(PsiModifier.PRIVATE) || classModifiers.hasModifierProperty(PsiModifier.PACKAGE_LOCAL))) {
191 return doAddLanguageAnnotation(project, parameterIndex >0? psiMethod.getParameterList().getParameters()[parameterIndex - 1] : psiMethod, languageId);
194 final String className = containingClass.getQualifiedName();
195 assert className != null;
196 final MethodParameterInjection injection = new MethodParameterInjection();
197 injection.setInjectedLanguageId(languageId);
198 injection.setClassName(className);
199 injection.setApplyInHierarchy(true);
200 final MethodParameterInjection.MethodInfo info = MethodParameterInjection.createMethodInfo(psiMethod);
201 if (parameterIndex < 0) info.setReturnFlag(true);
202 else info.getParamFlags()[parameterIndex] = true;
203 injection.setMethodInfos(Collections.singletonList(info));
204 doEditInjection(project, injection);
205 return true;
208 private static int findParameterIndex(final PsiElement target, final PsiExpressionList parent) {
209 final int idx = Arrays.asList(parent.getExpressions()).indexOf(target);
210 return idx < 0? -2 : idx;
213 @Nullable
214 private static PsiMethod findPsiMethod(final PsiElement parent) {
215 if (parent instanceof PsiNameValuePair) {
216 final PsiAnnotation annotation = PsiTreeUtil.getParentOfType(parent, PsiAnnotation.class);
217 if (annotation != null) {
218 final PsiJavaCodeReferenceElement referenceElement = annotation.getNameReferenceElement();
219 if (referenceElement != null) {
220 PsiElement resolved = referenceElement.resolve();
221 if (resolved != null) {
222 PsiMethod[] methods = ((PsiClass)resolved).findMethodsByName(((PsiNameValuePair)parent).getName(), false);
223 if (methods.length == 1) {
224 return methods[0];
230 final PsiMethod first;
231 if (parent.getParent() instanceof PsiMethodCallExpression) {
232 first = ((PsiMethodCallExpression)parent.getParent()).resolveMethod();
234 else {
235 first = PsiTreeUtil.getParentOfType(parent, PsiMethod.class, false);
237 if (first == null || first.getContainingClass() == null) return null;
238 final LinkedList<PsiMethod> methods = new LinkedList<PsiMethod>();
239 methods.add(first);
240 while (!methods.isEmpty()) {
241 final PsiMethod method = methods.removeFirst();
242 final PsiClass psiClass = method.getContainingClass();
243 if (psiClass != null && psiClass.getQualifiedName() != null) {
244 return method;
246 else {
247 methods.addAll(Arrays.asList(method.findSuperMethods()));
250 return null;
253 private static boolean doInjectInXmlText(final Project project, final XmlText host, final String languageId) {
254 final XmlTag tag = host.getParentTag();
255 if (tag != null) {
256 final XmlTagInjection injection = new XmlTagInjection();
257 injection.setInjectedLanguageId(languageId);
258 injection.setTagName(tag.getLocalName());
259 injection.setTagNamespace(tag.getNamespace());
260 doEditInjection(project, injection);
261 return true;
263 return false;
266 private static boolean doInjectInAttributeValue(final Project project, final XmlAttributeValue host, final String languageId) {
267 final XmlAttribute attribute = PsiTreeUtil.getParentOfType(host, XmlAttribute.class, true);
268 final XmlTag tag = attribute == null? null : attribute.getParent();
269 if (tag != null) {
270 final XmlAttributeInjection injection = new XmlAttributeInjection();
271 injection.setInjectedLanguageId(languageId);
272 injection.setAttributeName(attribute.getLocalName());
273 injection.setAttributeNamespace(attribute.getNamespace());
274 injection.setTagName(tag.getLocalName());
275 injection.setTagNamespace(tag.getNamespace());
276 doEditInjection(project, injection);
277 return true;
279 return false;
282 private static boolean doChooseLanguageToInject(final Processor<String> onChosen) {
283 final String[] langIds = InjectedLanguage.getAvailableLanguageIDs();
284 Arrays.sort(langIds);
286 final Map<String, List<String>> map = new LinkedHashMap<String, List<String>>();
287 buildLanguageTree(langIds, map);
289 final BaseListPopupStep<String> step = new MyPopupStep(map, new ArrayList<String>(map.keySet()), onChosen);
291 final ListPopup listPopup = JBPopupFactory.getInstance().createListPopup(step);
292 listPopup.showInBestPositionFor(DataManager.getInstance().getDataContext());
293 return true;
296 private static void doEditInjection(final Project project, final XmlAttributeInjection injection) {
297 final Configuration configuration = Configuration.getInstance();
298 final XmlAttributeInjection existing = configuration.findExistingInjection(injection);
299 if (doEditConfigurable(project, new XmlAttributeInjectionConfigurable(existing == null ? injection : existing, null, project))) {
300 if (existing == null) {
301 configuration.getAttributeInjections().add(injection);
302 configuration.configurationModified();
307 private static void doEditInjection(final Project project, final XmlTagInjection injection) {
308 final Configuration configuration = Configuration.getInstance();
309 final XmlTagInjection existing = configuration.findExistingInjection(injection);
310 if (doEditConfigurable(project, new XmlTagInjectionConfigurable(existing == null? injection : existing, null, project))) {
311 if (existing == null) {
312 configuration.getTagInjections().add(injection);
313 configuration.configurationModified();
318 private static void doEditInjection(final Project project, final MethodParameterInjection injection) {
319 final Configuration configuration = Configuration.getInstance();
320 final MethodParameterInjection existing = configuration.findExistingInjection(injection);
321 if (existing != null) {
322 // merge method infos
323 boolean found = false;
324 final MethodParameterInjection.MethodInfo curInfo = injection.getMethodInfos().iterator().next();
325 for (MethodParameterInjection.MethodInfo info : existing.getMethodInfos()) {
326 if (Comparing.equal(info.getMethodSignature(), curInfo.getMethodSignature())) {
327 found = true;
328 final boolean[] flags = curInfo.getParamFlags();
329 for (int i = 0; i < flags.length; i++) {
330 if (flags[i]) {
331 info.getParamFlags()[i] = true;
334 if (!info.isReturnFlag() && curInfo.isReturnFlag()) info.setReturnFlag(true);
337 if (!found) {
338 final ArrayList<MethodParameterInjection.MethodInfo> methodInfos = new ArrayList<MethodParameterInjection.MethodInfo>(existing.getMethodInfos());
339 methodInfos.add(curInfo);
340 existing.setMethodInfos(methodInfos);
343 if (doEditConfigurable(project, new MethodParameterInjectionConfigurable(existing == null? injection : existing, null, project))) {
344 if (existing == null) {
345 configuration.getParameterInjections().add(injection);
347 configuration.configurationModified();
352 private static boolean doEditConfigurable(final Project project, final Configurable configurable) {
353 return true; //ShowSettingsUtil.getInstance().editConfigurable(project, configurable);
356 private static void buildLanguageTree(String[] langIds, Map<String, List<String>> map) {
357 for (final String id : langIds) {
358 if (!map.containsKey(id)) {
359 map.put(id, new ArrayList<String>());
364 public boolean startInWriteAction() {
365 return false;
368 private static class MyPopupStep extends BaseListPopupStep<String> {
369 private final Map<String, List<String>> myMap;
370 private final Processor<String> myFinalStepProcessor;
372 public MyPopupStep(final Map<String, List<String>> map, final List<String> values, final Processor<String> finalStepProcessor) {
373 super("Choose Language", values);
374 myMap = map;
375 myFinalStepProcessor = finalStepProcessor;
378 @Override
379 public PopupStep onChosen(final String selectedValue, boolean finalChoice) {
380 if (finalChoice) {
381 myFinalStepProcessor.process(selectedValue);
382 return FINAL_CHOICE;
384 return new MyPopupStep(myMap, myMap.get(selectedValue), myFinalStepProcessor);
387 @Override
388 public boolean hasSubstep(String selectedValue) {
389 return myMap.containsKey(selectedValue) && !myMap.get(selectedValue).isEmpty();
392 @Override
393 public Icon getIconFor(String aValue) {
394 final Language language = InjectedLanguage.findLanguageById(aValue);
395 assert language != null;
396 final FileType ft = language.getAssociatedFileType();
397 return ft != null ? ft.getIcon() : new EmptyIcon(16);
400 @NotNull
401 @Override
402 public String getTextFor(String value) {
403 final Language language = InjectedLanguage.findLanguageById(value);
404 assert language != null;
405 final FileType ft = language.getAssociatedFileType();
406 return value + (ft != null ? " ("+ft.getDescription()+")" : "");