update copyright
[fedora-idea.git] / plugins / IntelliLang / src / org / intellij / plugins / intelliLang / PatternBasedInjectionHelper.java
blob71e6bd075247a069d07f6ac455468aadd900cc1f
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;
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;
40 import java.util.*;
42 /**
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()) {
56 // // place
57 // list.add(psiElement().inside(psiElement(JavaElementType.RETURN_STATEMENT).inside(psiMethod().withName(methodName).definedInClass(className))));
58 // // and owner:
59 // list.add(psiMethod().withName(methodName).definedInClass(className));
60 // }
61 // final boolean[] paramFlags = info.getParamFlags();
62 // for (int i = 0, paramFlagsLength = paramFlags.length; i < paramFlagsLength; i++) {
63 // if (paramFlags[i]) {
64 // // place
65 // list.add(psiElement().methodCallParameter(i, psiMethod().withName(methodName).withParameterCount(paramFlagsLength).definedInClass(className)));
66 // // and owner:
67 // list.add(psiParameter().ofMethod(i, psiMethod().withName(methodName).withParameterCount(paramFlagsLength).definedInClass(className)));
68 // }
69 // }
70 // }
71 // return StandardPatterns.or(list.toArray(new ElementPattern[list.size()]));
72 //}
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++) {
86 if (paramFlags[i]) {
87 list.add(getPatternStringForJavaPlace(methodName, typesString, i, className));
91 return list;
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(", ");
101 final int idx;
102 if (i == 0) {
103 // nothing
105 else {
106 sb.append('\"');
107 if ((idx = token.indexOf(' ')) > -1) {
108 sb.append(token.substring(0, idx));
110 else {
111 sb.append(token);
113 sb.append('\"');
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) {
128 sb.append(")");
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();
154 @Nullable
155 public static ElementPattern<PsiElement> createElementPattern(final String text, final String displayName, final String supportId) {
156 return createElementPatternNoGroovy(text, displayName, supportId);
159 //@Nullable
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);
168 // }
169 // }
171 // final ExpandoMetaClass metaClass = new ExpandoMetaClass(Object.class, false, metaMethods.toArray(new MetaMethod[metaMethods.size()]));
172 // final GroovyShell shell = new GroovyShell(binding);
173 // try {
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;
179 // }
180 // catch (GroovyRuntimeException ex) {
181 // Configuration.LOG.warn("error processing place: "+displayName+" ["+text+"]", ex);
182 // }
183 // return null;
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()]);
196 // without Groovy
197 @Nullable
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());
211 }));
212 try {
213 return createElementPatternNoGroovy(text, new Function<Frame, Object>() {
214 public Object fun(final Frame frame) {
215 try {
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);
227 return null;
231 private static enum State {
232 init, name, name_end,
233 param_start, param_end, literal,
234 invoke, invoke_end
237 private static class Frame {
238 State state = State.init;
239 Object target;
240 String methodName;
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>();
246 int curPos = 0;
247 Frame curFrame = new Frame();
248 Object curResult = null;
249 final StringBuilder curString = new StringBuilder();
250 while (true) {
251 if (curPos > text.length()) break;
252 final char ch = curPos++ < text.length()? text.charAt(curPos-1) : 0;
253 switch (curFrame.state) {
254 case init:
255 if (Character.isJavaIdentifierStart(ch)) {
256 curString.append(ch);
257 curFrame.state = State.name;
259 else {
260 throw new IllegalStateException("method call expected");
262 break;
263 case name:
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;
272 else {
273 throw new IllegalStateException("'"+curString+ch+"' method name start is invalid");
275 break;
276 case name_end:
277 if (ch == '(') {
278 curFrame.state = State.param_start;
280 else if (!Character.isWhitespace(ch)) {
281 throw new IllegalStateException("'(' expected after '"+curFrame.methodName+"'");
283 break;
284 case param_start:
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;
300 else {
301 throw new IllegalStateException("expression expected in '" + curFrame.methodName + "' call");
303 break;
304 case param_end:
305 if (ch == ')') {
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");
314 break;
315 case literal:
316 if (curString.charAt(0) == '\"') {
317 curString.append(ch);
318 if (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;
330 else {
331 curString.append(ch);
333 break;
334 case invoke:
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;
343 curResult = null;
345 else if (ch == ',' || ch == ')') {
346 curFrame = stack.pop();
347 curFrame.params.add(curResult);
348 curResult = null;
349 curFrame.state = ch == ')' ? State.invoke : State.param_start;
351 else if (Character.isWhitespace(ch)) {
352 curFrame.state = State.invoke_end;
354 else {
355 throw new IllegalStateException((stack.isEmpty()? "'.' or <eof>" : "'.' or ')'")
356 + "expected after '" + curFrame.methodName + "' call");
358 break;
359 case invoke_end:
360 if (ch == ')') {
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;
370 curResult = null;
372 else if (!Character.isWhitespace(ch)) {
373 throw new IllegalStateException((stack.isEmpty()? "'.' or <eof>" : "'.' or ')'")
374 + "expected after '" + curFrame.methodName + "' call");
376 break;
379 return null;
382 private static Object makeParam(final String s) {
383 if (s.length() > 2 && s.startsWith("\"") && s.endsWith("\"")) return s.substring(1, s.length()-1);
384 try {
385 return Integer.valueOf(s);
387 catch (NumberFormatException e) {}
388 return s;
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;
401 return type;
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;
425 try {
426 final Object[] newArgs;
427 if (!performArgConversion) newArgs = arguments;
428 else {
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));