| /* |
| * 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.jetbrains.plugins.groovy.lang.psi.impl; |
| |
| import com.intellij.ProjectTopics; |
| import com.intellij.openapi.components.ServiceManager; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.roots.ModuleRootAdapter; |
| import com.intellij.openapi.roots.ModuleRootEvent; |
| import com.intellij.openapi.util.Computable; |
| import com.intellij.openapi.util.RecursionGuard; |
| import com.intellij.openapi.util.RecursionManager; |
| import com.intellij.psi.*; |
| import com.intellij.psi.impl.PsiManagerEx; |
| import com.intellij.psi.search.GlobalSearchScope; |
| import com.intellij.psi.util.InheritanceUtil; |
| import com.intellij.util.ConcurrencyUtil; |
| import com.intellij.util.Function; |
| import com.intellij.util.IncorrectOperationException; |
| import com.intellij.util.containers.*; |
| import com.intellij.util.messages.MessageBusConnection; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElement; |
| import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElementFactory; |
| import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrTypeDefinition; |
| import org.jetbrains.plugins.groovy.lang.psi.util.GroovyCommonClassNames; |
| import org.jetbrains.plugins.groovy.lang.resolve.ResolveUtil; |
| |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.concurrent.ConcurrentMap; |
| |
| /** |
| * @author ven |
| */ |
| public class GroovyPsiManager { |
| private static final Logger LOG = Logger.getInstance("org.jetbrains.plugins.groovy.lang.psi.impl.GroovyPsiManager"); |
| private static final Set<String> ourPopularClasses = ContainerUtil.newHashSet(GroovyCommonClassNames.GROOVY_LANG_CLOSURE, |
| GroovyCommonClassNames.DEFAULT_BASE_CLASS_NAME, |
| GroovyCommonClassNames.GROOVY_OBJECT_SUPPORT, |
| GroovyCommonClassNames.GROOVY_LANG_SCRIPT, |
| CommonClassNames.JAVA_UTIL_LIST, |
| CommonClassNames.JAVA_UTIL_COLLECTION, |
| CommonClassNames.JAVA_LANG_STRING); |
| private final Project myProject; |
| |
| private final Map<String, GrTypeDefinition> myArrayClass = new HashMap<String, GrTypeDefinition>(); |
| |
| private final ConcurrentMap<GroovyPsiElement, PsiType> myCalculatedTypes = new ConcurrentWeakHashMap<GroovyPsiElement, PsiType>(); |
| private final Map<String, Map<GlobalSearchScope, PsiClass>> myClassCache = new ConcurrentSoftValueHashMap<String, Map<GlobalSearchScope, PsiClass>>(); |
| private final ConcurrentMap<PsiMember, Boolean> myCompileStatic = new ConcurrentHashMap<PsiMember, Boolean>(); |
| |
| private static final RecursionGuard ourGuard = RecursionManager.createGuard("groovyPsiManager"); |
| |
| public GroovyPsiManager(Project project) { |
| myProject = project; |
| |
| ((PsiManagerEx)PsiManager.getInstance(myProject)).registerRunnableToRunOnAnyChange(new Runnable() { |
| @Override |
| public void run() { |
| dropTypesCache(); |
| } |
| }); |
| ((PsiManagerEx)PsiManager.getInstance(myProject)).registerRunnableToRunOnChange(new Runnable() { |
| @Override |
| public void run() { |
| myClassCache.clear(); |
| } |
| }); |
| |
| final MessageBusConnection connection = myProject.getMessageBus().connect(); |
| connection.subscribe(ProjectTopics.PROJECT_ROOTS, new ModuleRootAdapter() { |
| @Override |
| public void rootsChanged(ModuleRootEvent event) { |
| dropTypesCache(); |
| myClassCache.clear(); |
| } |
| }); |
| } |
| |
| public void dropTypesCache() { |
| myCalculatedTypes.clear(); |
| myCompileStatic.clear(); |
| } |
| |
| public static boolean isInheritorCached(@Nullable PsiClass aClass, @NotNull String baseClassName) { |
| if (aClass == null) return false; |
| |
| return InheritanceUtil.isInheritorOrSelf(aClass, getInstance(aClass.getProject()).findClassWithCache(baseClassName, aClass.getResolveScope()), true); |
| } |
| |
| public static boolean isInheritorCached(@Nullable PsiType type, @NotNull String baseClassName) { |
| if (type instanceof PsiClassType) { |
| return isInheritorCached(((PsiClassType)type).resolve(), baseClassName); |
| } |
| return false; |
| } |
| |
| public static GroovyPsiManager getInstance(Project project) { |
| return ServiceManager.getService(project, GroovyPsiManager.class); |
| } |
| |
| public PsiClassType createTypeByFQClassName(@NotNull String fqName, @NotNull GlobalSearchScope resolveScope) { |
| if (ourPopularClasses.contains(fqName)) { |
| PsiClass result = findClassWithCache(fqName, resolveScope); |
| if (result != null) { |
| return JavaPsiFacade.getElementFactory(myProject).createType(result); |
| } |
| } |
| |
| return JavaPsiFacade.getElementFactory(myProject).createTypeByFQClassName(fqName, resolveScope); |
| } |
| |
| public boolean isCompileStatic(@NotNull PsiMember member) { |
| Boolean aBoolean = myCompileStatic.get(member); |
| if (aBoolean == null) { |
| aBoolean = ConcurrencyUtil.cacheOrGet(myCompileStatic, member, isCompileStaticInner(member)); |
| } |
| return aBoolean; |
| } |
| |
| private boolean isCompileStaticInner(@NotNull PsiMember member) { |
| PsiModifierList list = member.getModifierList(); |
| if (list != null) { |
| PsiAnnotation compileStatic = list.findAnnotation(GroovyCommonClassNames.GROOVY_TRANSFORM_COMPILE_STATIC); |
| if (compileStatic != null) return checkForPass(compileStatic); |
| PsiAnnotation typeChecked = list.findAnnotation(GroovyCommonClassNames.GROOVY_TRANSFORM_TYPE_CHECKED); |
| if (typeChecked != null) return checkForPass(typeChecked); |
| } |
| PsiClass aClass = member.getContainingClass(); |
| if (aClass != null) return isCompileStatic(aClass); |
| return false; |
| } |
| |
| private static boolean checkForPass(@NotNull PsiAnnotation annotation) { |
| PsiAnnotationMemberValue value = annotation.findAttributeValue("value"); |
| return value == null || |
| value instanceof PsiReference && |
| ResolveUtil.isEnumConstant((PsiReference)value, "PASS", GroovyCommonClassNames.GROOVY_TRANSFORM_TYPE_CHECKING_MODE); |
| } |
| |
| @Nullable |
| public PsiClass findClassWithCache(@NotNull String fqName, @NotNull GlobalSearchScope resolveScope) { |
| Map<GlobalSearchScope, PsiClass> map = myClassCache.get(fqName); |
| if (map == null) { |
| map = new ConcurrentHashMap<GlobalSearchScope, PsiClass>(); |
| myClassCache.put(fqName, map); |
| } |
| PsiClass cached = map.get(resolveScope); |
| if (cached != null) { |
| return cached; |
| } |
| |
| PsiClass result = JavaPsiFacade.getInstance(myProject).findClass(fqName, resolveScope); |
| if (result != null) { |
| map.put(resolveScope, result); |
| } |
| return result; |
| } |
| |
| private static final PsiType UNKNOWN_TYPE = new GrPsiTypeStub(); |
| |
| @Nullable |
| public <T extends GroovyPsiElement> PsiType getType(@NotNull T element, @NotNull Function<T, PsiType> calculator) { |
| PsiType type = myCalculatedTypes.get(element); |
| if (type == null) { |
| RecursionGuard.StackStamp stamp = ourGuard.markStack(); |
| type = calculator.fun(element); |
| if (type == null) { |
| type = UNKNOWN_TYPE; |
| } |
| if (stamp.mayCacheNow()) { |
| type = ConcurrencyUtil.cacheOrGet(myCalculatedTypes, element, type); |
| } else { |
| final PsiType alreadyInferred = myCalculatedTypes.get(element); |
| if (alreadyInferred != null) { |
| type = alreadyInferred; |
| } |
| } |
| } |
| if (!type.isValid()) { |
| error(element, type); |
| } |
| return UNKNOWN_TYPE == type ? null : type; |
| } |
| |
| private static void error(PsiElement element, PsiType type) { |
| LOG.error("Type is invalid: " + type + "; element: " + element + " of class " + element.getClass()); |
| } |
| |
| @Nullable |
| public GrTypeDefinition getArrayClass(@NotNull PsiType type) { |
| final String typeText = type.getCanonicalText(); |
| GrTypeDefinition definition = myArrayClass.get(typeText); |
| if (definition == null) { |
| try { |
| definition = GroovyPsiElementFactory.getInstance(myProject).createTypeDefinition("class __ARRAY__ { public int length; public " + typeText + "[] clone(){} }"); |
| myArrayClass.put(typeText, definition); |
| } |
| catch (IncorrectOperationException e) { |
| LOG.error(e); |
| return null; |
| } |
| } |
| |
| return definition; |
| } |
| |
| @Nullable |
| public static PsiType inferType(@NotNull PsiElement element, @NotNull Computable<PsiType> computable) { |
| List<Object> stack = ourGuard.currentStack(); |
| if (stack.size() > 7) { //don't end up walking the whole project PSI |
| ourGuard.prohibitResultCaching(stack.get(0)); |
| return null; |
| } |
| |
| return ourGuard.doPreventingRecursion(element, true, computable); |
| } |
| |
| } |