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
;
19 import com
.intellij
.openapi
.extensions
.Extensions
;
20 import com
.intellij
.openapi
.util
.Condition
;
21 import com
.intellij
.openapi
.util
.text
.StringUtil
;
22 import com
.intellij
.patterns
.ElementPattern
;
23 import com
.intellij
.psi
.PsiElement
;
24 import com
.intellij
.util
.Function
;
25 import com
.intellij
.util
.ReflectionCache
;
26 import com
.intellij
.util
.containers
.ContainerUtil
;
27 import com
.intellij
.util
.containers
.Stack
;
28 import gnu
.trove
.THashSet
;
29 import org
.intellij
.plugins
.intelliLang
.inject
.LanguageInjectionSupport
;
30 import org
.intellij
.plugins
.intelliLang
.inject
.config
.AbstractTagInjection
;
31 import org
.intellij
.plugins
.intelliLang
.inject
.config
.MethodParameterInjection
;
32 import org
.intellij
.plugins
.intelliLang
.inject
.config
.XmlAttributeInjection
;
33 import org
.jetbrains
.annotations
.NonNls
;
34 import org
.jetbrains
.annotations
.Nullable
;
36 import java
.lang
.reflect
.Array
;
37 import java
.lang
.reflect
.InvocationTargetException
;
38 import java
.lang
.reflect
.Method
;
39 import java
.lang
.reflect
.Modifier
;
43 * @author Gregory.Shrago
45 public class PatternBasedInjectionHelper
{
47 private PatternBasedInjectionHelper() {
50 //private static ElementPattern<PsiElement> createPatternFor(final MethodParameterInjection injection) {
51 // final ArrayList<ElementPattern<? extends PsiElement>> list = new ArrayList<ElementPattern<? extends PsiElement>>();
52 // final String className = injection.getClassName();
53 // for (MethodParameterInjection.MethodInfo info : injection.getMethodInfos()) {
54 // final String methodName = info.getMethodName();
55 // if (info.isReturnFlag()) {
57 // list.add(psiElement().inside(psiElement(JavaElementType.RETURN_STATEMENT).inside(psiMethod().withName(methodName).definedInClass(className))));
59 // list.add(psiMethod().withName(methodName).definedInClass(className));
61 // final boolean[] paramFlags = info.getParamFlags();
62 // for (int i = 0, paramFlagsLength = paramFlags.length; i < paramFlagsLength; i++) {
63 // if (paramFlags[i]) {
65 // list.add(psiElement().methodCallParameter(i, psiMethod().withName(methodName).withParameterCount(paramFlagsLength).definedInClass(className)));
67 // list.add(psiParameter().ofMethod(i, psiMethod().withName(methodName).withParameterCount(paramFlagsLength).definedInClass(className)));
71 // return StandardPatterns.or(list.toArray(new ElementPattern[list.size()]));
74 public static List
<String
> getPatternString(final MethodParameterInjection injection
) {
75 final ArrayList
<String
> list
= new ArrayList
<String
>();
76 final String className
= injection
.getClassName();
77 for (MethodParameterInjection
.MethodInfo info
: injection
.getMethodInfos()) {
78 final boolean[] paramFlags
= info
.getParamFlags();
79 final int paramFlagsLength
= paramFlags
.length
;
80 final String methodName
= info
.getMethodName();
81 final String typesString
= getParameterTypesString(info
.getMethodSignature());
82 if (info
.isReturnFlag()) {
83 list
.add(getPatternStringForJavaPlace(methodName
, typesString
, -1, className
));
85 for (int i
= 0; i
< paramFlagsLength
; i
++) {
87 list
.add(getPatternStringForJavaPlace(methodName
, typesString
, i
, className
));
94 public static String
getParameterTypesString(final String signature
) {
95 @NonNls final StringBuilder sb
= new StringBuilder();
96 final StringTokenizer st
= new StringTokenizer(signature
, "(,)");
97 //noinspection ForLoopThatDoesntUseLoopVariable
98 for (int i
= 0; st
.hasMoreTokens(); i
++) {
99 final String token
= st
.nextToken().trim();
100 if (i
> 1) sb
.append(", ");
107 if ((idx
= token
.indexOf(' ')) > -1) {
108 sb
.append(token
.substring(0, idx
));
116 return sb
.toString();
119 public static String
getPatternStringForJavaPlace(final String methodName
, final String parametersStrings
, final int parameterIndex
, final String className
) {
120 final StringBuilder sb
= new StringBuilder();
121 if (parameterIndex
>= 0) {
122 sb
.append("psiParameter().ofMethod(").append(parameterIndex
).append(", ");
124 sb
.append("psiMethod().withName(\"").append(methodName
)
125 .append("\").withParameters(").append(parametersStrings
)
126 .append(").definedInClass(\"").append(className
).append("\")");
127 if (parameterIndex
>= 0) {
130 return sb
.toString();
133 public static String
getPatternString(final XmlAttributeInjection injection
) {
134 final String name
= injection
.getAttributeName();
135 final String namespace
= injection
.getAttributeNamespace();
136 final StringBuilder result
= new StringBuilder("xmlAttribute()");
137 if (StringUtil
.isNotEmpty(name
)) result
.append(".withLocalName(string().matches(\"").append(name
).append("\"))");
138 if (StringUtil
.isNotEmpty(namespace
)) result
.append(".withNamespace(string().matches(\"").append(namespace
).append("\"))");
139 if (StringUtil
.isNotEmpty(injection
.getTagName()) || StringUtil
.isNotEmpty(injection
.getTagNamespace())) {
140 result
.append(".inside(").append(getPatternString((AbstractTagInjection
)injection
)).append(")");
142 return result
.toString();
145 public static String
getPatternString(final AbstractTagInjection injection
) {
146 final String name
= injection
.getTagName();
147 final String namespace
= injection
.getTagNamespace();
148 final StringBuilder result
= new StringBuilder("xmlTag()");
149 if (StringUtil
.isNotEmpty(name
)) result
.append(".withLocalName(string().matches(\"").append(name
).append("\"))");
150 if (StringUtil
.isNotEmpty(namespace
)) result
.append(".withNamespace(string().matches(\"").append(namespace
).append("\"))");
151 return result
.toString();
155 public static ElementPattern
<PsiElement
> createElementPattern(final String text
, final String displayName
, final String supportId
) {
156 return createElementPatternNoGroovy(text
, displayName
, supportId
);
160 //public static ElementPattern<PsiElement> createElementPatternGroovy(final String text, final String displayName, final String supportId) {
161 // final Binding binding = new Binding();
162 // final ArrayList<MetaMethod> metaMethods = new ArrayList<MetaMethod>();
163 // for (Class aClass : getPatternClasses(supportId)) {
164 // // walk super classes as well?
165 // for (CachedMethod method : ReflectionCache.getCachedClass(aClass).getMethods()) {
166 // if (!Modifier.isStatic(method.getModifiers()) || !Modifier.isPublic(method.getModifiers()) || Modifier.isAbstract(method.getModifiers())) continue;
167 // metaMethods.add(method);
171 // final ExpandoMetaClass metaClass = new ExpandoMetaClass(Object.class, false, metaMethods.toArray(new MetaMethod[metaMethods.size()]));
172 // final GroovyShell shell = new GroovyShell(binding);
174 // final Script script = shell.parse("return " + text);
175 // metaClass.initialize();
176 // script.setMetaClass(metaClass);
177 // final Object value = script.run();
178 // return value instanceof ElementPattern ? (ElementPattern<PsiElement>)value : null;
180 // catch (GroovyRuntimeException ex) {
181 // Configuration.LOG.warn("error processing place: "+displayName+" ["+text+"]", ex);
186 private static Class
[] getPatternClasses(final String supportId
) {
187 final ArrayList
<Class
> patternClasses
= new ArrayList
<Class
>();
188 for (LanguageInjectionSupport support
: Extensions
.getExtensions(LanguageInjectionSupport
.EP_NAME
)) {
189 if (supportId
== null || supportId
.equals(support
.getId())) {
190 patternClasses
.addAll(Arrays
.asList(support
.getPatternClasses()));
193 return patternClasses
.toArray(new Class
[patternClasses
.size()]);
198 public static ElementPattern
<PsiElement
> createElementPatternNoGroovy(final String text
, final String displayName
, final String supportId
) {
199 final Class
[] patternClasses
= getPatternClasses(supportId
);
200 final Set
<Method
> staticMethods
= new THashSet
<Method
>(ContainerUtil
.concat(patternClasses
, new Function
<Class
, Collection
<?
extends Method
>>() {
201 public Collection
<Method
> fun(final Class aClass
) {
202 return ContainerUtil
.findAll(ReflectionCache
.getMethods(aClass
), new Condition
<Method
>() {
203 public boolean value(final Method method
) {
204 return Modifier
.isStatic(method
.getModifiers())
205 && Modifier
.isPublic(method
.getModifiers())
206 && !Modifier
.isAbstract(method
.getModifiers())
207 && ElementPattern
.class.isAssignableFrom(method
.getReturnType());
213 return createElementPatternNoGroovy(text
, new Function
<Frame
, Object
>() {
214 public Object
fun(final Frame frame
) {
216 return invokeMethod(frame
.target
, frame
.methodName
, frame
.params
.toArray(), staticMethods
);
218 catch (Throwable throwable
) {
219 throw new RuntimeException(throwable
);
224 catch (Exception ex
) {
225 final Throwable cause
= ex
.getCause() != null? ex
.getCause() : ex
;
226 Configuration
.LOG
.warn("error processing place: " + displayName
+ " [" + text
+ "]", cause
);
231 private static enum State
{
232 init
, name
, name_end
,
233 param_start
, param_end
, literal
,
237 private static class Frame
{
238 State state
= State
.init
;
241 ArrayList
<Object
> params
= new ArrayList
<Object
>();
244 public static ElementPattern
<PsiElement
> createElementPatternNoGroovy(final String text
, final Function
<Frame
, Object
> executor
) {
245 final Stack
<Frame
> stack
= new Stack
<Frame
>();
247 Frame curFrame
= new Frame();
248 Object curResult
= null;
249 final StringBuilder curString
= new StringBuilder();
251 if (curPos
> text
.length()) break;
252 final char ch
= curPos
++ < text
.length()? text
.charAt(curPos
-1) : 0;
253 switch (curFrame
.state
) {
255 if (Character
.isJavaIdentifierStart(ch
)) {
256 curString
.append(ch
);
257 curFrame
.state
= State
.name
;
260 throw new IllegalStateException("method call expected");
264 if (Character
.isJavaIdentifierPart(ch
)) {
265 curString
.append(ch
);
267 else if (ch
== '(' || Character
.isWhitespace(ch
)) {
268 curFrame
.methodName
= curString
.toString();
269 curString
.setLength(0);
270 curFrame
.state
= ch
== '('? State
.param_start
: State
.name_end
;
273 throw new IllegalStateException("'"+curString
+ch
+"' method name start is invalid");
278 curFrame
.state
= State
.param_start
;
280 else if (!Character
.isWhitespace(ch
)) {
281 throw new IllegalStateException("'(' expected after '"+curFrame
.methodName
+"'");
285 if (Character
.isWhitespace(ch
)) {
287 else if (Character
.isDigit(ch
) || ch
== '\"') {
288 curFrame
.state
= State
.literal
;
289 curString
.append(ch
);
291 else if (ch
== ')') {
292 curFrame
.state
= State
.invoke
;
294 else if (Character
.isJavaIdentifierStart(ch
)) {
295 curString
.append(ch
);
296 stack
.push(curFrame
);
297 curFrame
= new Frame();
298 curFrame
.state
= State
.name
;
301 throw new IllegalStateException("expression expected in '" + curFrame
.methodName
+ "' call");
306 curFrame
.state
= State
.invoke
;
308 else if (ch
== ',') {
309 curFrame
.state
= State
.param_start
;
311 else if (!Character
.isWhitespace(ch
)) {
312 throw new IllegalStateException("')' or ',' expected in '" + curFrame
.methodName
+ "' call");
316 if (curString
.charAt(0) == '\"') {
317 curString
.append(ch
);
319 curFrame
.params
.add(makeParam(curString
.toString()));
320 curString
.setLength(0);
321 curFrame
.state
= State
.param_end
;
324 else if (Character
.isWhitespace(ch
) || ch
== ',' || ch
== ')') {
325 curFrame
.params
.add(makeParam(curString
.toString()));
326 curString
.setLength(0);
327 curFrame
.state
= ch
== ')' ? State
.invoke
:
328 ch
== ',' ? State
.param_start
: State
.param_end
;
331 curString
.append(ch
);
335 curResult
= executor
.fun(curFrame
);
336 if (ch
== 0 && stack
.isEmpty()) {
337 return (ElementPattern
<PsiElement
>)curResult
;
339 else if (ch
== '.') {
340 curFrame
= new Frame();
341 curFrame
.target
= curResult
;
342 curFrame
.state
= State
.init
;
345 else if (ch
== ',' || ch
== ')') {
346 curFrame
= stack
.pop();
347 curFrame
.params
.add(curResult
);
349 curFrame
.state
= ch
== ')' ? State
.invoke
: State
.param_start
;
351 else if (Character
.isWhitespace(ch
)) {
352 curFrame
.state
= State
.invoke_end
;
355 throw new IllegalStateException((stack
.isEmpty()?
"'.' or <eof>" : "'.' or ')'")
356 + "expected after '" + curFrame
.methodName
+ "' call");
361 curFrame
.state
= State
.invoke
;
363 else if (ch
== ',') {
364 curFrame
.state
= State
.param_start
;
366 else if (ch
== '.') {
367 curFrame
= new Frame();
368 curFrame
.target
= curResult
;
369 curFrame
.state
= State
.init
;
372 else if (!Character
.isWhitespace(ch
)) {
373 throw new IllegalStateException((stack
.isEmpty()?
"'.' or <eof>" : "'.' or ')'")
374 + "expected after '" + curFrame
.methodName
+ "' call");
382 private static Object
makeParam(final String s
) {
383 if (s
.length() > 2 && s
.startsWith("\"") && s
.endsWith("\"")) return s
.substring(1, s
.length()-1);
385 return Integer
.valueOf(s
);
387 catch (NumberFormatException e
) {}
391 public static Class
<?
> getNonPrimitiveType(final Class
<?
> type
) {
392 if (!type
.isPrimitive()) return type
;
393 if (type
== boolean.class) return Boolean
.class;
394 if (type
== byte.class) return Byte
.class;
395 if (type
== short.class) return Short
.class;
396 if (type
== int.class) return Integer
.class;
397 if (type
== long.class) return Long
.class;
398 if (type
== float.class) return Float
.class;
399 if (type
== double.class) return Double
.class;
400 if (type
== char.class) return Character
.class;
404 private static Object
invokeMethod(@Nullable final Object target
, final String methodName
, final Object
[] arguments
, final Collection
<Method
> staticMethods
) throws Throwable
{
405 main
: for (Method method
: target
== null? staticMethods
: Arrays
.asList(target
.getClass().getMethods())) {
406 if (!methodName
.equals(method
.getName())) continue;
407 final Class
<?
>[] parameterTypes
= method
.getParameterTypes();
408 if (!method
.isVarArgs() && parameterTypes
.length
!= arguments
.length
) continue;
409 boolean performArgConversion
= false;
410 for (int i
= 0, parameterTypesLength
= parameterTypes
.length
; i
< arguments
.length
; i
++) {
411 final Class
<?
> type
= getNonPrimitiveType(i
< parameterTypesLength ? parameterTypes
[i
] : parameterTypes
[parameterTypesLength
- 1]);
412 final Object argument
= arguments
[i
];
413 final Class
<?
> componentType
=
414 method
.isVarArgs() && i
< parameterTypesLength
- 1 ?
null : parameterTypes
[parameterTypesLength
- 1].getComponentType();
415 if (argument
!= null) {
416 if (!type
.isInstance(argument
)) {
417 if ((componentType
== null || !componentType
.isInstance(argument
))) continue main
;
418 else performArgConversion
= true;
422 if (parameterTypes
.length
> arguments
.length
) {
423 performArgConversion
= true;
426 final Object
[] newArgs
;
427 if (!performArgConversion
) newArgs
= arguments
;
429 newArgs
= new Object
[parameterTypes
.length
];
430 System
.arraycopy(arguments
, 0, newArgs
, 0, parameterTypes
.length
- 1);
431 final Object
[] varArgs
= (Object
[])Array
.newInstance(parameterTypes
[parameterTypes
.length
- 1].getComponentType(), arguments
.length
- parameterTypes
.length
+ 1);
432 System
.arraycopy(arguments
, parameterTypes
.length
- 1, varArgs
, 0, varArgs
.length
);
433 newArgs
[parameterTypes
.length
- 1] = varArgs
;
435 return method
.invoke(target
, newArgs
);
437 catch (InvocationTargetException e
) {
438 throw e
.getTargetException();
441 throw new NoSuchMethodException(methodName
+ ":" + Arrays
.asList(arguments
));