blob: 80d4c60a60848523cca5812ea68e5f0a8a3b46d0 [file] [log] [blame]
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* 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 org.intellij.plugins.intelliLang.inject.groovy;
import com.intellij.lang.Language;
import com.intellij.lang.injection.ConcatenationAwareInjector;
import com.intellij.lang.injection.MultiHostRegistrar;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.Trinity;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil;
import com.intellij.psi.injection.ReferenceInjector;
import com.intellij.psi.search.LocalSearchScope;
import com.intellij.psi.search.searches.ReferencesSearch;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.Processor;
import com.intellij.util.containers.ContainerUtil;
import gnu.trove.THashSet;
import org.intellij.plugins.intelliLang.Configuration;
import org.intellij.plugins.intelliLang.inject.InjectedLanguage;
import org.intellij.plugins.intelliLang.inject.InjectorUtils;
import org.intellij.plugins.intelliLang.inject.LanguageInjectionSupport;
import org.intellij.plugins.intelliLang.inject.TemporaryPlacesRegistry;
import org.intellij.plugins.intelliLang.inject.config.BaseInjection;
import org.intellij.plugins.intelliLang.inject.java.InjectionCache;
import org.intellij.plugins.intelliLang.inject.java.JavaLanguageInjectionSupport;
import org.intellij.plugins.intelliLang.util.AnnotationUtilEx;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.groovy.codeInspection.utils.ControlFlowUtils;
import org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes;
import org.jetbrains.plugins.groovy.lang.psi.GroovyFileBase;
import org.jetbrains.plugins.groovy.lang.psi.api.GroovyResolveResult;
import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.annotation.GrAnnotationNameValuePair;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.arguments.GrArgumentList;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrCodeBlock;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.branch.GrReturnStatement;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.*;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.literals.GrStringInjection;
import org.jetbrains.plugins.groovy.lang.psi.api.types.GrCodeReferenceElement;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
/**
* Created by Max Medvedev on 9/9/13
*/
public class GrConcatenationAwareInjector implements ConcatenationAwareInjector {
private final LanguageInjectionSupport mySupport;
private final Configuration myConfiguration;
private final Project myProject;
@SuppressWarnings("UnusedParameters")
public GrConcatenationAwareInjector(Configuration configuration, Project project, TemporaryPlacesRegistry temporaryPlacesRegistry) {
myConfiguration = configuration;
myProject = project;
mySupport = InjectorUtils.findNotNullInjectionSupport(GroovyLanguageInjectionSupport.GROOVY_SUPPORT_ID);
}
@Override
public void getLanguagesToInject(@NotNull final MultiHostRegistrar registrar, @NotNull final PsiElement... operands) {
if (operands.length == 0) return;
final PsiFile file = operands[0].getContainingFile();
if (!(file instanceof GroovyFileBase)) return;
new InjectionProcessor(myConfiguration, mySupport, operands) {
@Override
protected void processInjection(Language language,
List<Trinity<PsiLanguageInjectionHost, InjectedLanguage, TextRange>> list,
boolean settingsAvailable,
boolean unparsable) {
InjectorUtils.registerInjection(language, list, file, registrar);
InjectorUtils.registerSupport(mySupport, settingsAvailable, registrar);
InjectorUtils.putInjectedFileUserData(registrar, InjectedLanguageUtil.FRANKENSTEIN_INJECTION, unparsable);
}
@Override
protected boolean areThereInjectionsWithName(String methodName, boolean annoOnly) {
if (getAnnotatedElementsValue().contains(methodName)) {
return true;
}
if (!annoOnly && getXmlAnnotatedElementsValue().contains(methodName)) {
return true;
}
return false;
}
}.processInjections();
}
@NotNull
private static String getStringPresentation(@Nullable PsiElement operand) {
if (operand instanceof GrStringInjection) {
return operand.getText();
}
return "missingValue";
}
@Nullable
private String findLanguage(PsiElement[] operands) {
PsiElement parent = PsiTreeUtil.findCommonParent(operands);
BaseInjection params = GrConcatenationInjector.findLanguageParams(parent, myConfiguration);
if (params != null) {
return params.getInjectedLanguageId();
}
return null;
}
static class InjectionProcessor {
private final Configuration myConfiguration;
private final LanguageInjectionSupport mySupport;
private final PsiElement[] myOperands;
private boolean myShouldStop;
private boolean myUnparsable;
public InjectionProcessor(@NotNull Configuration configuration, @NotNull LanguageInjectionSupport support, @NotNull PsiElement... operands) {
myConfiguration = configuration;
mySupport = support;
myOperands = operands;
}
void processInjections() {
final PsiElement firstOperand = myOperands[0];
final PsiElement topBlock = ControlFlowUtils.findControlFlowOwner(firstOperand);
final LocalSearchScope searchScope = new LocalSearchScope(new PsiElement[]{topBlock instanceof GrCodeBlock
? topBlock : firstOperand.getContainingFile()}, "", true);
final THashSet<PsiModifierListOwner> visitedVars = new THashSet<PsiModifierListOwner>();
final LinkedList<PsiElement> places = new LinkedList<PsiElement>();
places.add(firstOperand);
final GrInjectionUtil.AnnotatedElementVisitor visitor = new GrInjectionUtil.AnnotatedElementVisitor() {
@Override
public boolean visitMethodParameter(GrExpression expression, GrCall methodCall) {
final GrArgumentList list = methodCall.getArgumentList();
assert list != null;
final String methodName;
if (methodCall instanceof GrMethodCall) {
GrExpression invoked = ((GrMethodCall)methodCall).getInvokedExpression();
final String referenceName = invoked instanceof GrReferenceExpression? ((GrReferenceExpression)invoked).getReferenceName() : null;
if ("super".equals(referenceName) || "this".equals(referenceName)) { // constructor call
final PsiClass psiClass = PsiTreeUtil.getParentOfType(methodCall, PsiClass.class, true);
final PsiClass psiTargetClass = "super".equals(referenceName)? psiClass == null ? null : psiClass.getSuperClass() : psiClass;
methodName = psiTargetClass == null? null : psiTargetClass.getName();
}
else {
methodName = referenceName;
}
}
else if (methodCall instanceof GrNewExpression) {
final GrCodeReferenceElement classRef = ((GrNewExpression)methodCall).getReferenceElement();
methodName = classRef == null ? null : classRef.getReferenceName();
}
else {
methodName = null;
}
if (methodName != null && areThereInjectionsWithName(methodName, false)) {
final GroovyResolveResult result = methodCall.advancedResolve();
PsiElement element = result.getElement();
if (element instanceof PsiMethod) {
PsiMethod method = (PsiMethod)element;
final PsiParameter[] parameters = method.getParameterList().getParameters();
int index = GrInjectionUtil.findParameterIndex(expression, methodCall);
if (index >= 0) {
process(parameters[index], method, index);
}
}
}
return false;
}
@Override
public boolean visitMethodReturnStatement(GrReturnStatement parent, PsiMethod method) {
if (areThereInjectionsWithName(method.getName(), false)) {
process(method, method, -1);
}
return false;
}
@Override
public boolean visitVariable(PsiVariable variable) {
if (myConfiguration.getAdvancedConfiguration().getDfaOption() != Configuration.DfaOption.OFF && visitedVars.add(variable)) {
ReferencesSearch.search(variable, searchScope).forEach(new Processor<PsiReference>() {
@Override
public boolean process(PsiReference psiReference) {
final PsiElement element = psiReference.getElement();
if (element instanceof GrExpression) {
final GrExpression refExpression = (GrExpression)element;
places.add(refExpression);
if (!myUnparsable) {
myUnparsable = checkUnparsableReference(refExpression);
}
}
return true;
}
});
}
if (!processCommentInjections(variable)) {
myShouldStop = true;
}
else if (areThereInjectionsWithName(variable.getName(), false)) {
process(variable, null, -1);
}
return false;
}
@Override
public boolean visitAnnotationParameter(GrAnnotationNameValuePair nameValuePair, PsiAnnotation psiAnnotation) {
final String paramName = nameValuePair.getName();
final String methodName = paramName != null ? paramName : PsiAnnotation.DEFAULT_REFERENCED_METHOD_NAME;
if (areThereInjectionsWithName(methodName, false)) {
final PsiReference reference = nameValuePair.getReference();
final PsiElement element = reference == null ? null : reference.resolve();
if (element instanceof PsiMethod) {
process((PsiMethod)element, (PsiMethod)element, -1);
}
}
return false;
}
@Override
public boolean visitReference(GrReferenceExpression expression) {
if (myConfiguration.getAdvancedConfiguration().getDfaOption() == Configuration.DfaOption.OFF) return true;
final PsiElement e = expression.resolve();
if (e instanceof PsiVariable) {
if (e instanceof PsiParameter) {
final PsiParameter p = (PsiParameter)e;
final PsiElement declarationScope = p.getDeclarationScope();
final PsiMethod method = declarationScope instanceof PsiMethod ? (PsiMethod)declarationScope : null;
final PsiParameterList parameterList = method == null ? null : method.getParameterList();
// don't check catchblock parameters & etc.
if (!(parameterList == null || parameterList != e.getParent()) &&
areThereInjectionsWithName(method.getName(), false)) {
final int parameterIndex = parameterList.getParameterIndex((PsiParameter)e);
process((PsiModifierListOwner)e, method, parameterIndex);
}
}
visitVariable((PsiVariable)e);
}
return !myShouldStop;
}
};
while (!places.isEmpty() && !myShouldStop) {
final PsiElement curPlace = places.removeFirst();
GrInjectionUtil.visitAnnotatedElements(curPlace, visitor);
}
}
private void process(final PsiModifierListOwner owner, PsiMethod method, int paramIndex) {
if (!processAnnotationInjections(owner)) {
myShouldStop = true;
}
for (BaseInjection injection : myConfiguration.getInjections(JavaLanguageInjectionSupport.JAVA_SUPPORT_ID)) {
if (injection.acceptsPsiElement(owner)) {
if (!processXmlInjections(injection, owner, method, paramIndex)) {
myShouldStop = true;
break;
}
}
}
}
private boolean processAnnotationInjections(final PsiModifierListOwner annoElement) {
final String checkName;
if (annoElement instanceof PsiParameter) {
final PsiElement scope = ((PsiParameter)annoElement).getDeclarationScope();
checkName = scope instanceof PsiMethod ? ((PsiNamedElement)scope).getName() : ((PsiNamedElement)annoElement).getName();
}
else if (annoElement instanceof PsiNamedElement) {
checkName = ((PsiNamedElement)annoElement).getName();
}
else checkName = null;
if (checkName == null || !areThereInjectionsWithName(checkName, true)) return true;
final PsiAnnotation[] annotations =
GrConcatenationInjector.getAnnotationFrom(annoElement, myConfiguration.getAdvancedConfiguration().getLanguageAnnotationPair(), true, true);
if (annotations.length > 0) {
return processAnnotationInjectionInner(annoElement, annotations);
}
return true;
}
private boolean checkUnparsableReference(GrExpression expression) {
final PsiElement parent = expression.getParent();
if (parent instanceof GrAssignmentExpression) {
final GrAssignmentExpression assignmentExpression = (GrAssignmentExpression)parent;
final IElementType operation = assignmentExpression.getOperationTokenType();
if (assignmentExpression.getLValue() == expression && operation == GroovyTokenTypes.mPLUS_ASSIGN ) {
return true;
}
}
else if (parent instanceof GrBinaryExpression) {
return true;
}
return false;
}
protected boolean areThereInjectionsWithName(String methodName, boolean annoOnly) {
return true;
}
protected boolean processCommentInjectionInner(PsiVariable owner, PsiElement comment, BaseInjection injection) {
processInjectionWithContext(injection, false);
return false;
}
protected boolean processAnnotationInjectionInner(PsiModifierListOwner owner, PsiAnnotation[] annotations) {
final String id = AnnotationUtilEx.calcAnnotationValue(annotations, "value");
final String prefix = AnnotationUtilEx.calcAnnotationValue(annotations, "prefix");
final String suffix = AnnotationUtilEx.calcAnnotationValue(annotations, "suffix");
final BaseInjection injection = new BaseInjection(JavaLanguageInjectionSupport.JAVA_SUPPORT_ID);
if (prefix != null) injection.setPrefix(prefix);
if (suffix != null) injection.setSuffix(suffix);
if (id != null) injection.setInjectedLanguageId(id);
processInjectionWithContext(injection, false);
return false;
}
protected boolean processXmlInjections(BaseInjection injection, PsiModifierListOwner owner, PsiMethod method, int paramIndex) {
processInjectionWithContext(injection, true);
if (injection.isTerminal()) {
return false;
}
return true;
}
private boolean processCommentInjections(PsiVariable owner) {
Ref<PsiElement> causeRef = Ref.create();
PsiElement anchor = owner.getFirstChild() instanceof PsiComment?
(owner.getModifierList() != null? owner.getModifierList() : owner.getTypeElement()) : owner;
if (anchor == null) return true;
BaseInjection injection = mySupport.findCommentInjection(anchor, causeRef);
return injection == null || processCommentInjectionInner(owner, causeRef.get(), injection);
}
private void processInjectionWithContext(BaseInjection injection, boolean settingsAvailable) {
Language language = InjectedLanguage.findLanguageById(injection.getInjectedLanguageId());
if (language == null) {
ReferenceInjector injector = ReferenceInjector.findById(injection.getInjectedLanguageId());
if (injector != null) {
language = injector.toLanguage();
}
else return;
}
String languageID = language.getID();
List<Trinity<PsiLanguageInjectionHost, InjectedLanguage, TextRange>> list = ContainerUtil.newArrayList();
boolean unparsable = false;
String prefix = "";
//String suffix = "";
for (int i = 0; i < myOperands.length; i++) {
PsiElement operand = myOperands[i];
final ElementManipulator<PsiElement> manipulator = ElementManipulators.getManipulator(operand);
if (manipulator == null) {
unparsable = true;
prefix += getStringPresentation(operand);
if (i == myOperands.length - 1) {
Trinity<PsiLanguageInjectionHost, InjectedLanguage, TextRange> last = ContainerUtil.getLastItem(list);
assert last != null;
InjectedLanguage injected = last.second;
list.set(list.size() - 1, Trinity.create(last.first, InjectedLanguage.create(injected.getID(), injected.getPrefix(), prefix, false), last.third));
}
}
else {
InjectedLanguage injectedLanguage = InjectedLanguage.create(languageID, prefix, "", false);
TextRange range = manipulator.getRangeInElement(operand);
PsiLanguageInjectionHost host = (PsiLanguageInjectionHost)operand;
list.add(Trinity.create(host, injectedLanguage, range));
prefix = "";
}
}
if (!list.isEmpty()) {
processInjection(language, list, settingsAvailable, unparsable);
}
}
protected void processInjection(Language language,
List<Trinity<PsiLanguageInjectionHost, InjectedLanguage, TextRange>> list,
boolean settingsAvailable, boolean unparsable) {
}
}
public Collection<String> getAnnotatedElementsValue() {
// note: external annotations not supported
return InjectionCache.getInstance(myProject).getAnnoIndex();
}
private Collection<String> getXmlAnnotatedElementsValue() {
return InjectionCache.getInstance(myProject).getXmlIndex();
}
}