blob: 65c6d88bfc6d19e5f44c88b8eb8727601ec5e995 [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.java;
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.openapi.util.text.StringUtil;
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.psi.util.PsiUtil;
import com.intellij.util.ArrayUtil;
import com.intellij.util.Processor;
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.util.AnnotationUtilEx;
import org.intellij.plugins.intelliLang.util.ContextComputationProcessor;
import org.intellij.plugins.intelliLang.util.PsiUtilEx;
import org.jetbrains.annotations.NotNull;
import java.util.*;
/**
* @author cdr
*/
public class ConcatenationInjector implements ConcatenationAwareInjector {
private final Configuration myConfiguration;
private final Project myProject;
private final TemporaryPlacesRegistry myTemporaryPlacesRegistry;
private final LanguageInjectionSupport mySupport;
public ConcatenationInjector(Configuration configuration, Project project, TemporaryPlacesRegistry temporaryPlacesRegistry) {
myConfiguration = configuration;
myProject = project;
myTemporaryPlacesRegistry = temporaryPlacesRegistry;
mySupport = InjectorUtils.findNotNullInjectionSupport(JavaLanguageInjectionSupport.JAVA_SUPPORT_ID);
}
public void getLanguagesToInject(@NotNull final MultiHostRegistrar registrar, @NotNull PsiElement... operands) {
if (operands.length == 0) return;
boolean hasLiteral = false;
InjectedLanguage tempInjectedLanguage = null;
PsiFile containingFile = null;
for (PsiElement operand : operands) {
if (PsiUtilEx.isStringOrCharacterLiteral(operand)) {
if (containingFile == null) {
containingFile = operands[0].getContainingFile();
}
tempInjectedLanguage = myTemporaryPlacesRegistry.getLanguageFor((PsiLanguageInjectionHost)operand, containingFile);
hasLiteral = true;
if (tempInjectedLanguage != null) break;
}
}
if (!hasLiteral) return;
final Language tempLanguage = tempInjectedLanguage == null ? null : tempInjectedLanguage.getLanguage();
final PsiFile finalContainingFile = containingFile;
InjectionProcessor injectionProcessor = 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, finalContainingFile, registrar);
InjectorUtils.registerSupport(mySupport, settingsAvailable, registrar);
InjectorUtils.putInjectedFileUserData(registrar, InjectedLanguageUtil.FRANKENSTEIN_INJECTION, unparsable ? Boolean.TRUE : null);
}
@Override
protected boolean areThereInjectionsWithName(String methodName, boolean annoOnly) {
if (getAnnotatedElementsValue().contains(methodName)) {
return true;
}
if (!annoOnly && getXmlAnnotatedElementsValue().contains(methodName)) {
return true;
}
return false;
}
};
if (tempLanguage != null) {
BaseInjection baseInjection = new BaseInjection(JavaLanguageInjectionSupport.JAVA_SUPPORT_ID);
baseInjection.setInjectedLanguageId(tempInjectedLanguage.getID());
injectionProcessor.processInjectionInner(baseInjection, false);
InjectorUtils.putInjectedFileUserData(registrar, LanguageInjectionSupport.TEMPORARY_INJECTED_LANGUAGE, tempInjectedLanguage);
}
else {
injectionProcessor.processInjections();
}
}
public static class InjectionProcessor {
private final Configuration myConfiguration;
private final LanguageInjectionSupport mySupport;
private final PsiElement[] myOperands;
private boolean myShouldStop;
private boolean myUnparsable;
public InjectionProcessor(Configuration configuration, LanguageInjectionSupport support, PsiElement... operands) {
myConfiguration = configuration;
mySupport = support;
myOperands = operands;
}
public void processInjections() {
final PsiElement firstOperand = myOperands[0];
final PsiElement topBlock = PsiUtil.getTopLevelEnclosingCodeBlock(firstOperand, null);
final LocalSearchScope searchScope = new LocalSearchScope(new PsiElement[]{topBlock instanceof PsiCodeBlock
? topBlock : firstOperand.getContainingFile()}, "", true);
final THashSet<PsiModifierListOwner> visitedVars = new THashSet<PsiModifierListOwner>();
final LinkedList<PsiElement> places = new LinkedList<PsiElement>();
places.add(firstOperand);
final AnnotationUtilEx.AnnotatedElementVisitor visitor = new AnnotationUtilEx.AnnotatedElementVisitor() {
public boolean visitMethodParameter(PsiExpression expression, PsiCallExpression psiCallExpression) {
final PsiExpressionList list = psiCallExpression.getArgumentList();
assert list != null;
final int index = ArrayUtil.indexOf(list.getExpressions(), expression);
final String methodName;
if (psiCallExpression instanceof PsiMethodCallExpression) {
final String referenceName = ((PsiMethodCallExpression)psiCallExpression).getMethodExpression().getReferenceName();
if ("super".equals(referenceName) || "this".equals(referenceName)) { // constructor call
final PsiClass psiClass = PsiTreeUtil.getParentOfType(psiCallExpression, 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 (psiCallExpression instanceof PsiNewExpression) {
final PsiJavaCodeReferenceElement classRef = ((PsiNewExpression)psiCallExpression).getClassOrAnonymousClassReference();
methodName = classRef == null ? null : classRef.getReferenceName();
}
else {
methodName = null;
}
if (methodName != null && areThereInjectionsWithName(methodName, false)) {
final PsiMethod method = psiCallExpression.resolveMethod();
final PsiParameter[] parameters = method == null ? PsiParameter.EMPTY_ARRAY : method.getParameterList().getParameters();
if (index >= 0 && index < parameters.length && method != null) {
process(parameters[index], method, index);
}
}
return false;
}
public boolean visitMethodReturnStatement(PsiReturnStatement parent, PsiMethod method) {
if (areThereInjectionsWithName(method.getName(), false)) {
process(method, method, -1);
}
return false;
}
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 PsiExpression) {
final PsiExpression refExpression = (PsiExpression)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;
}
public boolean visitAnnotationParameter(PsiNameValuePair 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;
}
public boolean visitReference(PsiReferenceExpression 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();
AnnotationUtilEx.visitAnnotatedElements(curPlace, visitor);
}
}
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);
}
protected boolean processCommentInjectionInner(PsiVariable owner, PsiElement comment, BaseInjection injection) {
processInjectionWithContext(injection, false);
return false;
}
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 =
AnnotationUtilEx.getAnnotationFrom(annoElement, myConfiguration.getAdvancedConfiguration().getLanguageAnnotationPair(), true);
if (annotations.length > 0) {
return processAnnotationInjectionInner(annoElement, annotations);
}
return true;
}
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;
}
protected void processInjectionInner(BaseInjection injection, boolean settingsAvailable) {
processInjectionWithContext(injection, settingsAvailable);
}
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;
}
final boolean separateFiles = !injection.isSingleFile() && StringUtil.isNotEmpty(injection.getValuePattern());
final Ref<Boolean> unparsableRef = Ref.create(myUnparsable);
final List<Object> objects = ContextComputationProcessor.collectOperands(injection.getPrefix(), injection.getSuffix(), unparsableRef, myOperands);
if (objects.isEmpty()) return;
final List<Trinity<PsiLanguageInjectionHost, InjectedLanguage, TextRange>> result =
new ArrayList<Trinity<PsiLanguageInjectionHost, InjectedLanguage, TextRange>>();
final int len = objects.size();
for (int i = 0; i < len; i++) {
String curPrefix = null;
Object o = objects.get(i);
if (o instanceof String) {
curPrefix = (String)o;
if (i == len - 1) return; // IDEADEV-26751
o = objects.get(++i);
}
String curSuffix = null;
PsiLanguageInjectionHost curHost = null;
if (o instanceof PsiLanguageInjectionHost) {
curHost = (PsiLanguageInjectionHost)o;
if (i == len - 2) {
final Object next = objects.get(i + 1);
if (next instanceof String) {
i++;
curSuffix = (String)next;
}
}
}
if (curHost == null) {
unparsableRef.set(Boolean.TRUE);
}
else {
if (!(curHost instanceof PsiLiteralExpression)) {
TextRange textRange = ElementManipulators.getManipulator(curHost).getRangeInElement(curHost);
TextRange.assertProperRange(textRange, injection);
result.add(Trinity.create(curHost, InjectedLanguage.create(injection.getInjectedLanguageId(), curPrefix, curSuffix, true),
textRange));
}
else {
final List<TextRange> injectedArea = injection.getInjectedArea(curHost);
for (int j = 0, injectedAreaSize = injectedArea.size(); j < injectedAreaSize; j++) {
TextRange textRange = injectedArea.get(j);
TextRange.assertProperRange(textRange, injection);
result.add(Trinity.create(
curHost, InjectedLanguage.create(injection.getInjectedLanguageId(),
(separateFiles || j == 0 ? curPrefix : ""),
(separateFiles || j == injectedAreaSize - 1 ? curSuffix : ""),
true), textRange));
}
}
}
}
if (!result.isEmpty()) {
if (separateFiles) {
for (Trinity<PsiLanguageInjectionHost, InjectedLanguage, TextRange> trinity : result) {
processInjection(language, Collections.singletonList(trinity), settingsAvailable, false);
}
}
else {
processInjection(language, result, settingsAvailable, unparsableRef.get());
}
}
}
protected void processInjection(Language language,
List<Trinity<PsiLanguageInjectionHost, InjectedLanguage, TextRange>> list,
boolean xmlInjection,
boolean unparsable) {
}
protected boolean areThereInjectionsWithName(String methodName, boolean annoOnly) {
return true;
}
}
private static boolean checkUnparsableReference(final PsiExpression refExpression) {
final PsiElement parent = refExpression.getParent();
if (parent instanceof PsiAssignmentExpression) {
final PsiAssignmentExpression assignmentExpression = (PsiAssignmentExpression)parent;
final IElementType operation = assignmentExpression.getOperationTokenType();
if (assignmentExpression.getLExpression() == refExpression && JavaTokenType.PLUSEQ.equals(operation)) {
return true;
}
}
else if (parent instanceof PsiPolyadicExpression) {
return true;
}
return false;
}
public Collection<String> getAnnotatedElementsValue() {
// note: external annotations not supported
return InjectionCache.getInstance(myProject).getAnnoIndex();
}
private Collection<String> getXmlAnnotatedElementsValue() {
return InjectionCache.getInstance(myProject).getXmlIndex();
}
}