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
;
57 * @author Gregory.Shrago
59 public class JavaLanguageInjectionSupport
implements LanguageInjectionSupport
{
61 private static boolean isMine(final PsiLanguageInjectionHost psiElement
) {
62 return PsiUtilEx
.isStringOrCharacterLiteral(psiElement
);
66 public String
getId() {
67 return JAVA_SUPPORT_ID
;
71 public Class
[] getPatternClasses() {
72 return new Class
[] { PsiJavaPatterns
.class };
75 public boolean useDefaultInjector(final PsiElement host
) {
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
);
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
);
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() {
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());
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;
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();
195 final PsiAnnotation existingAnnotation
= list
.findAnnotation(annotationName
);
196 if (existingAnnotation
!= null) {
197 existingAnnotation
.replace(annotation
);
200 list
.addAfter(annotation
, null);
202 JavaCodeStyleManager
.getInstance(getProject()).shortenClassReferences(list
);
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);
235 info
.getParamFlags()[parameterIndex
] = true;
237 injection
.setMethodInfos(Collections
.singletonList(info
));
238 doEditInjection(project
, injection
, psiMethod
);
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
;
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) {
264 final PsiMethod first
;
265 if (parent
.getParent() instanceof PsiMethodCallExpression
) {
266 first
= ((PsiMethodCallExpression
)parent
.getParent()).resolveMethod();
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
>();
274 while (!methods
.isEmpty()) {
275 final PsiMethod method
= methods
.removeFirst();
276 final PsiClass psiClass
= method
.getContainingClass();
277 if (psiClass
!= null && psiClass
.getQualifiedName() != null) {
281 methods
.addAll(Arrays
.asList(method
.findSuperMethods()));
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
;
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
);
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;
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
);
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
;
364 infos
.add(methodInfo
);
367 result
.setMethodInfos(infos
);
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
);