| /* |
| * 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(); |
| } |
| } |