/* * 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.openapi.fileTypes.StdFileTypes; import com.intellij.openapi.progress.EmptyProgressIndicator; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Key; import com.intellij.openapi.util.text.StringUtil; import com.intellij.patterns.compiler.PatternClassBean; import com.intellij.patterns.compiler.PatternCompilerFactory; import com.intellij.psi.*; import com.intellij.psi.impl.cache.CacheManager; import com.intellij.psi.impl.search.LowLevelSearchUtil; import com.intellij.psi.impl.source.resolve.FileContextUtil; import com.intellij.psi.scope.PsiScopeProcessor; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.search.TextOccurenceProcessor; import com.intellij.psi.search.UsageSearchContext; import com.intellij.psi.util.*; import com.intellij.psi.xml.XmlTag; import com.intellij.psi.xml.XmlText; import com.intellij.util.ArrayUtil; import com.intellij.util.Processor; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.SoftFactoryMap; import com.intellij.util.text.StringSearcher; import org.intellij.plugins.intelliLang.inject.InjectorUtils; import org.intellij.plugins.intelliLang.inject.config.BaseInjection; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.plugins.groovy.GroovyFileType; import org.jetbrains.plugins.groovy.lang.resolve.NonCodeMembersContributor; import java.util.List; import java.util.Set; /** * @author Gregory.Shrago */ public class PatternEditorContextMembersProvider extends NonCodeMembersContributor { private static final Key<SoftFactoryMap<Class[], PsiFile>> PATTERN_INJECTION_CONTEXT = Key.create("PATTERN_INJECTION_CONTEXT"); private static final Key<CachedValue<Set<String>>> PATTERN_CLASSES = Key.create("PATTERN_CLASSES"); @Override public void processDynamicElements(@NotNull PsiType qualifierType, @NotNull final PsiScopeProcessor scopeProcessor, @NotNull final PsiElement place, @NotNull final ResolveState state) { final PsiFile file = place.getContainingFile().getOriginalFile(); final BaseInjection injection = file.getUserData(BaseInjection.INJECTION_KEY); Processor<PsiElement> processor = element -> element.processDeclarations(scopeProcessor, state, null, place); if (injection == null) { processDevContext(file, processor); } else { processPatternContext(injection, file, processor); } } private static boolean processPatternContext(@NotNull BaseInjection injection, @NotNull PsiFile file, @NotNull Processor<PsiElement> processor) { return processor.process(getRootByClasses(file, InjectorUtils.getPatternClasses(injection.getSupportId()))); } @NotNull private static PsiFile getRootByClasses(@NotNull PsiFile file, @NotNull Class[] classes) { final Project project = file.getProject(); SoftFactoryMap<Class[], PsiFile> map = project.getUserData(PATTERN_INJECTION_CONTEXT); if (map == null) { map = new SoftFactoryMap<Class[], PsiFile>() { @Override protected PsiFile create(Class[] key) { String text = PatternCompilerFactory.getFactory().getPatternCompiler(key).dumpContextDeclarations(); return PsiFileFactory.getInstance(project).createFileFromText("context.groovy", GroovyFileType.GROOVY_FILE_TYPE, text); } }; project.putUserData(PATTERN_INJECTION_CONTEXT, map); } return map.get(classes); } private static boolean processDevContext(final PsiFile file, Processor<PsiElement> processor) { final XmlTag tag = getTagByInjectedFile(file); final XmlTag parentTag = tag == null ? null : tag.getParentTag(); final String parentTagName = parentTag == null ? null : parentTag.getName(); final String name = tag == null ? null : tag.getName(); if ("place".equals(name) && "injection".equals(parentTagName)) { return processRootsByClassNames(file, parentTag.getAttributeValue("injector-id"), processor); } else if ("pattern".equals(name) && parentTag != null) { return processRootsByClassNames(file, tag.getAttributeValue("type"), processor); } return true; } @Nullable private static XmlTag getTagByInjectedFile(final PsiFile file) { final SmartPsiElementPointer pointer = file.getUserData(FileContextUtil.INJECTED_IN_ELEMENT); final PsiElement element = pointer == null? null : pointer.getElement(); return element instanceof XmlText ? ((XmlText)element).getParentTag() : null; } private static boolean processRootsByClassNames(@NotNull PsiFile file, @Nullable String type, @NotNull Processor<PsiElement> processor) { Project project = file.getProject(); Set<String> classNames = collectDevPatternClassNames(project); if (!classNames.isEmpty()) { JavaPsiFacade psiFacade = JavaPsiFacade.getInstance(project); for (String className : classNames) { PsiClass patternClass = psiFacade.findClass(className, GlobalSearchScope.allScope(project)); if (patternClass != null && !processor.process(patternClass)) return false; } } Class[] classes = StringUtil.isEmpty(type) ? ArrayUtil.EMPTY_CLASS_ARRAY : PatternCompilerFactory.getFactory().getPatternClasses(type); return classes.length == 0 || processor.process(getRootByClasses(file, classes)); } private static Set<String> collectDevPatternClassNames(@NotNull final Project project) { CachedValue<Set<String>> cachedValue = project.getUserData(PATTERN_CLASSES); if (cachedValue == null) { cachedValue = CachedValuesManager.getManager(project).createCachedValue( () -> CachedValueProvider.Result.create(calcDevPatternClassNames(project), PsiModificationTracker.OUT_OF_CODE_BLOCK_MODIFICATION_COUNT), false); project.putUserData(PATTERN_CLASSES, cachedValue); } return cachedValue.getValue(); } private static Set<String> calcDevPatternClassNames(@NotNull final Project project) { final List<String> roots = ContainerUtil.createLockFreeCopyOnWriteList(); JavaPsiFacade psiFacade = JavaPsiFacade.getInstance(project); PsiClass beanClass = psiFacade.findClass(PatternClassBean.class.getName(), GlobalSearchScope.allScope(project)); if (beanClass != null) { GlobalSearchScope scope = GlobalSearchScope.getScopeRestrictedByFileTypes(GlobalSearchScope.allScope(project), StdFileTypes.XML); final TextOccurenceProcessor occurenceProcessor = (element, offsetInElement) -> { XmlTag tag = PsiTreeUtil.getParentOfType(element, XmlTag.class); String className = tag == null ? null : tag.getAttributeValue("className"); if (StringUtil.isNotEmpty(className) && tag.getLocalName().endsWith("patternClass")) { roots.add(className); } return true; }; final StringSearcher searcher = new StringSearcher("patternClass", true, true); CacheManager.SERVICE.getInstance(beanClass.getProject()).processFilesWithWord(psiFile -> { LowLevelSearchUtil.processElementsContainingWordInElement(occurenceProcessor, psiFile, searcher, true, new EmptyProgressIndicator()); return true; }, searcher.getPattern(), UsageSearchContext.IN_FOREIGN_LANGUAGES, scope, searcher.isCaseSensitive()); } return ContainerUtil.newHashSet(roots); } }