/* * Copyright 2012 CoreMedia AG * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an "AS * IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language * governing permissions and limitations under the License. */ package net.jangaroo.ide.idea.sith; import com.intellij.codeInsight.AnnotationUtil; import com.intellij.ide.highlighter.JavaFileType; import com.intellij.lang.javascript.JavascriptLanguage; import com.intellij.openapi.util.TextRange; import com.intellij.psi.InjectedLanguagePlaces; import com.intellij.psi.JavaTokenType; import com.intellij.psi.LanguageInjector; import com.intellij.psi.PsiAnnotation; import com.intellij.psi.PsiAnnotationMemberValue; import com.intellij.psi.PsiAnnotationParameterList; import com.intellij.psi.PsiArrayInitializerMemberValue; import com.intellij.psi.PsiClass; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiEllipsisType; import com.intellij.psi.PsiExpression; import com.intellij.psi.PsiExpressionList; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiJavaCodeReferenceElement; import com.intellij.psi.PsiJavaToken; import com.intellij.psi.PsiLanguageInjectionHost; import com.intellij.psi.PsiLiteralExpression; import com.intellij.psi.PsiMethod; import com.intellij.psi.PsiMethodCallExpression; import com.intellij.psi.PsiModifierList; import com.intellij.psi.PsiModifierListOwner; import com.intellij.psi.PsiNameValuePair; import com.intellij.psi.PsiParameter; import com.intellij.psi.PsiParameterList; import com.intellij.psi.PsiType; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.List; /** * JavaScript language injection in Java SITH JsProxy subclass source files. */ public class SithLanguageInjector implements LanguageInjector { private static final String JAVA_SCRIPT_EXPRESSION_ANNOTATION_CLASS_QNAME = "com.coremedia.uitesting.webdriver.access.JavaScriptExpression"; private static final String JAVA_SCRIPT_EXPRESSION_ANNOTATION_PARAMETERS_NAME = "parameters"; public void getLanguagesToInject(@NotNull PsiLanguageInjectionHost psiLanguageInjectionHost, @NotNull InjectedLanguagePlaces injectedLanguagePlaces) { PsiFile psiFile = psiLanguageInjectionHost.getContainingFile(); if (JavaFileType.INSTANCE.equals(psiFile.getFileType()) && getStringLiteralExpressionValue(psiLanguageInjectionHost) != null) { List<String> parameterNames = getParameterNamesIfIsJavaScriptExpression((PsiLiteralExpression)psiLanguageInjectionHost); if (parameterNames != null) { injectedLanguagePlaces.addPlace(JavascriptLanguage.INSTANCE, TextRange.from(1, psiLanguageInjectionHost.getTextLength() - 2), buildCodePrefix(parameterNames), ";}"); } } } @Nullable private List<String> getParameterNamesIfIsJavaScriptExpression(@NotNull PsiLiteralExpression argumentStringLiteralExpression) { PsiMethodCallExpression methodCallExpression = findMethodCallExpression(argumentStringLiteralExpression); if (methodCallExpression != null) { return getParameterNamesIfMethodCallArgumentIsJsExpression(argumentStringLiteralExpression, methodCallExpression); } PsiAnnotation annotation = findAnnotation(argumentStringLiteralExpression); if (annotation != null) { return getParameterNamesIfAnnotationArgumentIsJsExpression(argumentStringLiteralExpression, annotation); } return null; } @Nullable private static PsiMethodCallExpression findMethodCallExpression(@NotNull PsiLiteralExpression argument) { PsiElement argumentListCandidate = argument.getParent(); if (argumentListCandidate instanceof PsiExpressionList) { PsiElement methodCallCandidate = argumentListCandidate.getParent(); if (methodCallCandidate != null && methodCallCandidate instanceof PsiMethodCallExpression) { return (PsiMethodCallExpression)methodCallCandidate; } } return null; } @Nullable private static PsiAnnotation findAnnotation(@NotNull PsiLiteralExpression argument) { PsiElement nameValuePair = argument.getParent(); PsiElement annotationParameterListCandidate = nameValuePair.getParent(); if (annotationParameterListCandidate instanceof PsiAnnotationParameterList) { PsiElement annotationCandidate = annotationParameterListCandidate.getParent(); if (annotationCandidate instanceof PsiAnnotation) { return (PsiAnnotation)annotationCandidate; } } return null; } private List<String> getParameterNamesIfMethodCallArgumentIsJsExpression(@NotNull PsiLiteralExpression argumentStringLiteralExpression, @NotNull PsiMethodCallExpression methodCall) { List<String> parameterNames = null; PsiMethod method = (PsiMethod)methodCall.getMethodExpression().resolve(); if (method != null) { PsiParameterList parameterList = method.getParameterList(); PsiParameter[] parameters = parameterList.getParameters(); // find argumentStringLiteralExpression in argument list: PsiExpression[] arguments = methodCall.getArgumentList().getExpressions(); for (int i = 0; i < Math.min(arguments.length, parameters.length); i++) { // check whether parameter at same index has JavaScriptExpression annotation: if (arguments[i] == argumentStringLiteralExpression) { parameterNames = getParameterNamesIfHasJavaScriptExpressionAnnotation(parameters[i]); if (parameterNames != null) { addEllipsisArguments(parameters, arguments, i, parameterNames); } break; } } } return parameterNames; } private static void addEllipsisArguments(PsiParameter[] parameters, PsiExpression[] arguments, int expressionParameterIndex, @NotNull List<String> parameterNames) { if (expressionParameterIndex + 1 < parameters.length) { PsiType nextParameterType = parameters[expressionParameterIndex + 1].getType(); if (nextParameterType instanceof PsiEllipsisType) { if (Object.class.getName().equals(((PsiEllipsisType)nextParameterType).getComponentType().getCanonicalText())) { for (int j = expressionParameterIndex + 1; j < arguments.length; j += 2) { String parameterName = getStringLiteralExpressionValue(arguments[j]); if (parameterName != null) { parameterNames.add(parameterName); } } } } } } private List<String> getParameterNamesIfAnnotationArgumentIsJsExpression(PsiLiteralExpression argument, PsiAnnotation annotation) { PsiJavaCodeReferenceElement annotationNameReferenceElement = annotation.getNameReferenceElement(); if (annotationNameReferenceElement != null) { PsiElement annotationClass = annotationNameReferenceElement.resolve(); if (annotationClass instanceof PsiClass) { String parameterName = ((PsiNameValuePair)argument.getParent()).getName(); if (parameterName == null) { parameterName = PsiAnnotation.DEFAULT_REFERENCED_METHOD_NAME; } PsiMethod[] methods = ((PsiClass)annotationClass).getMethods(); for (PsiMethod method : methods) { if (parameterName.equals(method.getName())) { return getParameterNamesIfHasJavaScriptExpressionAnnotation(method); } } } } return null; } private static List<String> getParameterNamesIfHasJavaScriptExpressionAnnotation(@NotNull PsiModifierListOwner modifierListOwner) { PsiModifierList modifierList = modifierListOwner.getModifierList(); if (modifierList == null) { return null; } PsiAnnotation annotation = AnnotationUtil.findAnnotation(modifierListOwner, JAVA_SCRIPT_EXPRESSION_ANNOTATION_CLASS_QNAME); if (annotation == null) { return null; } PsiNameValuePair[] attributes = annotation.getParameterList().getAttributes(); List<String> parameterNames = new ArrayList<String>(); for (PsiNameValuePair attribute : attributes) { if (JAVA_SCRIPT_EXPRESSION_ANNOTATION_PARAMETERS_NAME.equals(attribute.getName())) { PsiAnnotationMemberValue value = attribute.getValue(); if (value instanceof PsiArrayInitializerMemberValue) { for (PsiAnnotationMemberValue initializer : ((PsiArrayInitializerMemberValue)value).getInitializers()) { parameterNames.add(getStringLiteralExpressionValue(initializer)); } } else { parameterNames.add(getStringLiteralExpressionValue(value)); } } } return parameterNames; } @NotNull private static String buildCodePrefix(@NotNull List<String> parameterNames) { StringBuilder prefix = new StringBuilder(); prefix.append("function("); for (int i = 0; i < parameterNames.size(); i++) { if (i > 0) { prefix.append(","); } prefix.append(parameterNames.get(i)); } prefix.append("){return "); return prefix.toString(); } @Nullable private static String getStringLiteralExpressionValue(PsiElement psiElement) { if (psiElement instanceof PsiLiteralExpression) { PsiLiteralExpression literalExpression = (PsiLiteralExpression)psiElement; final PsiElement child = literalExpression.getFirstChild(); if (child instanceof PsiJavaToken && ((PsiJavaToken)child).getTokenType() == JavaTokenType.STRING_LITERAL) { return (String)literalExpression.getValue(); } } return null; } }