do not inject into conditional part of ?: expression
[fedora-idea.git] / plugins / IntelliLang / src / org / intellij / plugins / intelliLang / inject / java / JavaLanguageInjectionSupport.java
blob21fe06a2987e050e61d853f6f8774cb31830cfe2
1 /*
2 * Copyright 2000-2009 JetBrains s.r.o.
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.
17 package org.intellij.plugins.intelliLang.inject.java;
19 import com.intellij.lang.Language;
20 import com.intellij.openapi.application.Result;
21 import com.intellij.openapi.command.WriteCommandAction;
22 import com.intellij.openapi.project.Project;
23 import com.intellij.openapi.ui.DialogBuilder;
24 import com.intellij.openapi.ui.DialogWrapper;
25 import com.intellij.openapi.util.text.StringUtil;
26 import com.intellij.openapi.options.Configurable;
27 import com.intellij.patterns.PsiJavaPatterns;
28 import com.intellij.psi.*;
29 import com.intellij.psi.codeStyle.JavaCodeStyleManager;
30 import com.intellij.psi.util.PsiTreeUtil;
31 import com.intellij.psi.util.PsiUtil;
32 import com.intellij.util.NullableFunction;
33 import com.intellij.util.Processor;
34 import com.intellij.util.containers.ContainerUtil;
35 import org.intellij.plugins.intelliLang.Configuration;
36 import org.intellij.plugins.intelliLang.PatternBasedInjectionHelper;
37 import org.intellij.plugins.intelliLang.AdvancedSettingsUI;
38 import org.intellij.plugins.intelliLang.inject.config.BaseInjection;
39 import org.intellij.plugins.intelliLang.inject.config.InjectionPlace;
40 import org.intellij.plugins.intelliLang.inject.config.MethodParameterInjection;
41 import org.intellij.plugins.intelliLang.inject.config.ui.AbstractInjectionPanel;
42 import org.intellij.plugins.intelliLang.inject.config.ui.MethodParameterPanel;
43 import org.intellij.plugins.intelliLang.inject.config.ui.configurables.MethodParameterInjectionConfigurable;
44 import org.intellij.plugins.intelliLang.inject.LanguageInjectionSupport;
45 import org.intellij.plugins.intelliLang.inject.EditInjectionSettingsAction;
46 import org.intellij.plugins.intelliLang.inject.InjectLanguageAction;
47 import org.intellij.plugins.intelliLang.util.AnnotationUtilEx;
48 import org.intellij.plugins.intelliLang.util.PsiUtilEx;
49 import org.intellij.plugins.intelliLang.util.ContextComputationProcessor;
50 import org.jdom.Element;
51 import org.jetbrains.annotations.NotNull;
52 import org.jetbrains.annotations.Nullable;
54 import java.util.*;
56 /**
57 * @author Gregory.Shrago
59 public class JavaLanguageInjectionSupport implements LanguageInjectionSupport {
61 private static boolean isMine(final PsiLanguageInjectionHost psiElement) {
62 return PsiUtilEx.isStringOrCharacterLiteral(psiElement);
65 @NotNull
66 public String getId() {
67 return JAVA_SUPPORT_ID;
70 @NotNull
71 public Class[] getPatternClasses() {
72 return new Class[] { PsiJavaPatterns.class };
75 public boolean useDefaultInjector(final PsiElement host) {
76 return false;
79 public Configurable[] createSettings(final Project project, final Configuration configuration) {
80 return new Configurable[]{new AdvancedSettingsUI(project, configuration)};
83 public boolean addInjectionInPlace(final Language language, final PsiLanguageInjectionHost psiElement) {
84 if (!isMine(psiElement)) return false;
85 return doInjectInJava(psiElement.getProject(), psiElement, language.getID());
88 public boolean removeInjectionInPlace(final PsiLanguageInjectionHost psiElement) {
89 if (!isMine(psiElement)) return false;
90 final Configuration configuration = Configuration.getInstance();
91 final HashMap<BaseInjection, ConcatenationInjector.Info> injectionsMap = new HashMap<BaseInjection, ConcatenationInjector.Info>();
92 final ArrayList<PsiAnnotation> annotations = new ArrayList<PsiAnnotation>();
93 final PsiLiteralExpression host = (PsiLiteralExpression)psiElement;
94 final Project project = host.getProject();
95 collectInjections(host, configuration, injectionsMap, annotations);
96 if (injectionsMap.isEmpty() && annotations.isEmpty()) return false;
97 final ArrayList<BaseInjection> originalInjections = new ArrayList<BaseInjection>(injectionsMap.keySet());
98 final List<BaseInjection> newInjections = ContainerUtil.mapNotNull(originalInjections, new NullableFunction<BaseInjection, BaseInjection>() {
99 public BaseInjection fun(final BaseInjection injection) {
100 final ConcatenationInjector.Info info = injectionsMap.get(injection);
101 final String placeText = getPatternStringForJavaPlace(info.method, info.parameterIndex);
102 final BaseInjection newInjection = injection.copy();
103 newInjection.setPlaceEnabled(placeText, false);
104 return newInjection.isEnabled() ? newInjection : null;
107 Configuration.getInstance().replaceInjectionsWithUndo(project, newInjections, originalInjections, annotations);
108 return true;
111 public boolean editInjectionInPlace(final PsiLanguageInjectionHost psiElement) {
112 if (!isMine(psiElement)) return false;
113 final Configuration configuration = Configuration.getInstance();
114 final HashMap<BaseInjection, ConcatenationInjector.Info> injectionsMap = new HashMap<BaseInjection, ConcatenationInjector.Info>();
115 final ArrayList<PsiAnnotation> annotations = new ArrayList<PsiAnnotation>();
116 final PsiLiteralExpression host = (PsiLiteralExpression)psiElement;
117 final Project project = host.getProject();
118 collectInjections(host, configuration, injectionsMap, annotations);
119 if (injectionsMap.isEmpty() || !annotations.isEmpty()) return false;
121 final BaseInjection originalInjection = injectionsMap.keySet().iterator().next();
122 final MethodParameterInjection methodParameterInjection = createMethodParameterInjection(originalInjection, injectionsMap.get(originalInjection).method, false);
123 final MethodParameterInjection savedCopy = methodParameterInjection.copy();
124 final AbstractInjectionPanel panel = new MethodParameterPanel(methodParameterInjection, project);
125 panel.reset();
126 final DialogBuilder builder = new DialogBuilder(project);
127 builder.addOkAction();
128 builder.addCancelAction();
129 builder.setCenterPanel(panel.getComponent());
130 builder.setTitle(EditInjectionSettingsAction.EDIT_INJECTION_TITLE);
131 builder.setOkOperation(new Runnable() {
132 public void run() {
133 panel.apply();
134 builder.getDialogWrapper().close(DialogWrapper.OK_EXIT_CODE);
137 if (builder.show() == DialogWrapper.OK_EXIT_CODE) {
138 methodParameterInjection.initializePlaces(false);
139 savedCopy.initializePlaces(false);
140 methodParameterInjection.mergeOriginalPlacesFrom(savedCopy, false);
141 final BaseInjection newInjection = new BaseInjection(methodParameterInjection.getSupportId()).copyFrom(methodParameterInjection);
142 newInjection.mergeOriginalPlacesFrom(originalInjection, true);
143 final List<BaseInjection> newInjections =
144 newInjection.isEnabled()? Collections.singletonList(newInjection) : Collections.<BaseInjection>emptyList();
145 Configuration.getInstance().replaceInjectionsWithUndo(project, newInjections, Collections.singletonList(originalInjection),
146 Collections.<PsiAnnotation>emptyList());
148 return true;
152 public BaseInjection createInjection(final Element element) {
153 if (element.getName().equals(MethodParameterInjection.class.getSimpleName())) {
154 return new MethodParameterInjection();
156 else return new BaseInjection(JAVA_SUPPORT_ID);
159 private static boolean doInjectInJava(final Project project, final PsiElement host, final String languageId) {
160 final PsiElement target = ContextComputationProcessor.getTopLevelInjectionTarget(host);
161 final PsiElement parent = target.getParent();
162 if (parent instanceof PsiReturnStatement ||
163 parent instanceof PsiMethod ||
164 parent instanceof PsiNameValuePair) {
165 return doInjectInJavaMethod(project, findPsiMethod(parent), -1, languageId);
167 else if (parent instanceof PsiExpressionList && parent.getParent() instanceof PsiMethodCallExpression) {
168 return doInjectInJavaMethod(project, findPsiMethod(parent), findParameterIndex(target, (PsiExpressionList)parent), languageId);
170 else if (parent instanceof PsiAssignmentExpression) {
171 final PsiExpression psiExpression = ((PsiAssignmentExpression)parent).getLExpression();
172 if (psiExpression instanceof PsiReferenceExpression) {
173 final PsiElement element = ((PsiReferenceExpression)psiExpression).resolve();
174 if (element != null) {
175 return doInjectInJava(project, element, languageId);
179 else if (parent instanceof PsiVariable) {
180 if (doAddLanguageAnnotation(project, (PsiModifierListOwner)parent, languageId)) return true;
182 return false;
185 static boolean doAddLanguageAnnotation(final Project project, final PsiModifierListOwner modifierListOwner,
186 final String languageId) {
187 if (modifierListOwner.getModifierList() == null || !PsiUtil.getLanguageLevel(modifierListOwner).hasEnumKeywordAndAutoboxing()) return false;
188 new WriteCommandAction(project, modifierListOwner.getContainingFile()) {
189 protected void run(final Result result) throws Throwable {
190 final String annotationName = org.intellij.lang.annotations.Language.class.getName();
191 final PsiAnnotation annotation = JavaPsiFacade.getInstance(project).getElementFactory()
192 .createAnnotationFromText("@" + annotationName + "(\"" + languageId + "\")", modifierListOwner);
193 final PsiModifierList list = modifierListOwner.getModifierList();
194 assert list != null;
195 final PsiAnnotation existingAnnotation = list.findAnnotation(annotationName);
196 if (existingAnnotation != null) {
197 existingAnnotation.replace(annotation);
199 else {
200 list.addAfter(annotation, null);
202 JavaCodeStyleManager.getInstance(getProject()).shortenClassReferences(list);
204 }.execute();
205 return true;
208 private static boolean doInjectInJavaMethod(final Project project, final PsiMethod psiMethod, final int parameterIndex,
209 final String languageId) {
210 if (psiMethod == null) return false;
211 if (parameterIndex < -1) return false;
212 if (parameterIndex >= psiMethod.getParameterList().getParametersCount()) return false;
213 final PsiModifierList methodModifiers = psiMethod.getModifierList();
214 if (methodModifiers.hasModifierProperty(PsiModifier.PRIVATE) || methodModifiers.hasModifierProperty(PsiModifier.PACKAGE_LOCAL)) {
215 return doAddLanguageAnnotation(project, parameterIndex >= 0? psiMethod.getParameterList().getParameters()[parameterIndex] : psiMethod, languageId);
217 final PsiClass containingClass = psiMethod.getContainingClass();
218 assert containingClass != null;
219 final PsiModifierList classModifiers = containingClass.getModifierList();
220 if (classModifiers != null && (classModifiers.hasModifierProperty(PsiModifier.PRIVATE) || classModifiers.hasModifierProperty(PsiModifier.PACKAGE_LOCAL))) {
221 return doAddLanguageAnnotation(project, parameterIndex >= 0? psiMethod.getParameterList().getParameters()[parameterIndex] : psiMethod, languageId);
224 final String className = containingClass.getQualifiedName();
225 assert className != null;
226 final MethodParameterInjection injection = new MethodParameterInjection();
227 injection.setInjectedLanguageId(languageId);
228 injection.setClassName(className);
229 injection.setApplyInHierarchy(true);
230 final MethodParameterInjection.MethodInfo info = MethodParameterInjection.createMethodInfo(psiMethod);
231 if (parameterIndex < 0) {
232 info.setReturnFlag(true);
234 else {
235 info.getParamFlags()[parameterIndex] = true;
237 injection.setMethodInfos(Collections.singletonList(info));
238 doEditInjection(project, injection, psiMethod);
239 return true;
242 static int findParameterIndex(final PsiElement target, final PsiExpressionList parent) {
243 final int idx = Arrays.<PsiElement>asList(parent.getExpressions()).indexOf(target);
244 return idx < 0? -2 : idx;
247 @Nullable
248 static PsiMethod findPsiMethod(final PsiElement parent) {
249 if (parent instanceof PsiNameValuePair) {
250 final PsiAnnotation annotation = PsiTreeUtil.getParentOfType(parent, PsiAnnotation.class);
251 if (annotation != null) {
252 final PsiJavaCodeReferenceElement referenceElement = annotation.getNameReferenceElement();
253 if (referenceElement != null) {
254 PsiElement resolved = referenceElement.resolve();
255 if (resolved != null) {
256 PsiMethod[] methods = ((PsiClass)resolved).findMethodsByName(((PsiNameValuePair)parent).getName(), false);
257 if (methods.length == 1) {
258 return methods[0];
264 final PsiMethod first;
265 if (parent.getParent() instanceof PsiMethodCallExpression) {
266 first = ((PsiMethodCallExpression)parent.getParent()).resolveMethod();
268 else {
269 first = PsiTreeUtil.getParentOfType(parent, PsiMethod.class, false);
271 if (first == null || first.getContainingClass() == null) return null;
272 final LinkedList<PsiMethod> methods = new LinkedList<PsiMethod>();
273 methods.add(first);
274 while (!methods.isEmpty()) {
275 final PsiMethod method = methods.removeFirst();
276 final PsiClass psiClass = method.getContainingClass();
277 if (psiClass != null && psiClass.getQualifiedName() != null) {
278 return method;
280 else {
281 methods.addAll(Arrays.asList(method.findSuperMethods()));
284 return null;
287 private static void doEditInjection(final Project project, final MethodParameterInjection template, final PsiMethod contextMethod) {
288 final Configuration configuration = Configuration.getInstance();
289 template.initializePlaces(false);
290 final BaseInjection baseTemplate = new BaseInjection(template.getSupportId()).copyFrom(template);
291 final MethodParameterInjection allMethodParameterInjection = createMethodParameterInjection(baseTemplate, contextMethod, true);
292 allMethodParameterInjection.initializePlaces(false);
293 // find existing injection for this class.
294 final BaseInjection originalInjection = configuration.findExistingInjection(allMethodParameterInjection);
295 final MethodParameterInjection methodParameterInjection;
296 if (originalInjection == null) {
297 methodParameterInjection = template;
299 else {
300 final BaseInjection originalCopy = originalInjection.copy();
301 final InjectionPlace currentPlace = template.getInjectionPlaces().get(0);
302 final String text = currentPlace.getText();
303 originalCopy.setPlaceEnabled(text, true);
304 methodParameterInjection = createMethodParameterInjection(originalCopy, contextMethod, false);
306 if (InjectLanguageAction.doEditConfigurable(project, new MethodParameterInjectionConfigurable(methodParameterInjection, null, project))) {
307 methodParameterInjection.initializePlaces(false);
308 final BaseInjection newInjection = new BaseInjection(methodParameterInjection.getSupportId()).copyFrom(methodParameterInjection);
309 newInjection.mergeOriginalPlacesFrom(originalInjection, true);
310 Configuration.getInstance().replaceInjectionsWithUndo(
311 project, Collections.singletonList(newInjection),
312 ContainerUtil.createMaybeSingletonList(originalInjection),
313 Collections.<PsiElement>emptyList());
317 private static void collectInjections(final PsiLiteralExpression host, final Configuration configuration,
318 final Map<BaseInjection, ConcatenationInjector.Info> injectionsToRemove,
319 final ArrayList<PsiAnnotation> annotationsToRemove) {
320 ConcatenationInjector.processLiteralExpressionInjectionsInner(configuration, new Processor<ConcatenationInjector.Info>() {
321 public boolean process(final ConcatenationInjector.Info info) {
322 final PsiAnnotation[] annotations = AnnotationUtilEx.getAnnotationFrom(info.owner, configuration.getLanguageAnnotationPair(), true);
323 annotationsToRemove.addAll(Arrays.asList(annotations));
324 for (BaseInjection injection : info.injections) {
325 injectionsToRemove.put(injection, info);
327 return true;
329 }, host);
332 private static MethodParameterInjection createMethodParameterInjection(final BaseInjection injection,
333 final PsiMethod contextMethod,
334 final boolean includeAllPlaces) {
335 final PsiClass containingClass = contextMethod.getContainingClass();
336 final String className = containingClass == null ? "" : StringUtil.notNullize(containingClass.getQualifiedName());
337 final MethodParameterInjection result = new MethodParameterInjection();
338 result.copyFrom(injection);
339 result.getInjectionPlaces().clear();
340 result.setClassName(className);
341 if (containingClass != null) {
342 final ArrayList<MethodParameterInjection.MethodInfo> infos = new ArrayList<MethodParameterInjection.MethodInfo>();
343 for (PsiMethod method : containingClass.getMethods()) {
344 final PsiModifierList modifiers = method.getModifierList();
345 if (modifiers.hasModifierProperty(PsiModifier.PRIVATE) || modifiers.hasModifierProperty(PsiModifier.PACKAGE_LOCAL)) continue;
346 boolean add = false;
347 final MethodParameterInjection.MethodInfo methodInfo = MethodParameterInjection.createMethodInfo(method);
348 if (MethodParameterInjection.isInjectable(method.getReturnType(), method.getProject())) {
349 final int parameterIndex = -1;
350 final InjectionPlace place = injection.findPlaceByText(getPatternStringForJavaPlace(method, parameterIndex));
351 methodInfo.setReturnFlag(place != null && place.isEnabled() || includeAllPlaces);
352 add = true;
354 final PsiParameter[] parameters = method.getParameterList().getParameters();
355 for (int i = 0; i < parameters.length; i++) {
356 final PsiParameter p = parameters[i];
357 if (MethodParameterInjection.isInjectable(p.getType(), p.getProject())) {
358 final InjectionPlace place = injection.findPlaceByText(getPatternStringForJavaPlace(method, i));
359 methodInfo.getParamFlags()[i] = place != null && place.isEnabled() || includeAllPlaces;
360 add = true;
363 if (add) {
364 infos.add(methodInfo);
367 result.setMethodInfos(infos);
369 return result;
372 public static String getPatternStringForJavaPlace(final PsiMethod method, final int parameterIndex) {
373 final PsiClass psiClass = method.getContainingClass();
374 final String className = psiClass == null ? "" : StringUtil.notNullize(psiClass.getQualifiedName());
375 final String signature = MethodParameterInjection.createMethodInfo(method).getMethodSignature();
376 return PatternBasedInjectionHelper.getPatternStringForJavaPlace(method.getName(), PatternBasedInjectionHelper.getParameterTypesString(signature), parameterIndex, className);