/* * Copyright 2000-2012 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 com.intellij.psi.impl.file; import com.intellij.codeInsight.completion.scope.JavaCompletionHints; import com.intellij.lang.Language; import com.intellij.lang.java.JavaLanguage; import com.intellij.navigation.ItemPresentation; import com.intellij.navigation.ItemPresentationProviders; import com.intellij.openapi.ui.Queryable; import com.intellij.openapi.util.Condition; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.*; import com.intellij.psi.impl.JavaPsiFacadeImpl; import com.intellij.psi.impl.source.tree.java.PsiCompositeModifierList; import com.intellij.psi.scope.ElementClassHint; import com.intellij.psi.scope.NameHint; import com.intellij.psi.scope.PsiScopeProcessor; import com.intellij.psi.search.DelegatingGlobalSearchScope; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.util.*; import com.intellij.reference.SoftReference; import com.intellij.util.CommonProcessors; import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Set; public class PsiPackageImpl extends PsiPackageBase implements PsiPackage, Queryable { public static boolean DEBUG = false; private volatile CachedValue<PsiModifierList> myAnnotationList; private volatile CachedValue<Collection<PsiDirectory>> myDirectories; private volatile CachedValue<Collection<PsiDirectory>> myDirectoriesWithLibSources; private volatile SoftReference<Set<String>> myPublicClassNamesCache; public PsiPackageImpl(PsiManager manager, String qualifiedName) { super(manager, qualifiedName); } @Override protected Collection<PsiDirectory> getAllDirectories(boolean includeLibrarySources) { if (includeLibrarySources) { if (myDirectoriesWithLibSources == null) { myDirectoriesWithLibSources = createCachedDirectories(true); } return myDirectoriesWithLibSources.getValue(); } else { if (myDirectories == null) { myDirectories = createCachedDirectories(false); } return myDirectories.getValue(); } } private CachedValue<Collection<PsiDirectory>> createCachedDirectories(final boolean includeLibrarySources) { return CachedValuesManager.getManager(myManager.getProject()).createCachedValue(new CachedValueProvider<Collection<PsiDirectory>>() { @Override public Result<Collection<PsiDirectory>> compute() { final CommonProcessors.CollectProcessor<PsiDirectory> processor = new CommonProcessors.CollectProcessor<PsiDirectory>(); getFacade().processPackageDirectories(PsiPackageImpl.this, allScope(), processor, includeLibrarySources); return Result.create(processor.getResults(), PsiPackageImplementationHelper.getInstance().getDirectoryCachedValueDependencies( PsiPackageImpl.this)); } }, false); } @Override protected PsiElement findPackage(String qName) { return getFacade().findPackage(qName); } @Override public void handleQualifiedNameChange(@NotNull final String newQualifiedName) { PsiPackageImplementationHelper.getInstance().handleQualifiedNameChange(this, newQualifiedName); } @Override public VirtualFile[] occursInPackagePrefixes() { return PsiPackageImplementationHelper.getInstance().occursInPackagePrefixes(this); } @Override public PsiPackageImpl getParentPackage() { return (PsiPackageImpl)super.getParentPackage(); } @Override protected PsiPackageImpl createInstance(PsiManager manager, String qName) { return new PsiPackageImpl(myManager, qName); } @Override @NotNull public Language getLanguage() { return JavaLanguage.INSTANCE; } @Override public boolean isValid() { return PsiPackageImplementationHelper.getInstance().packagePrefixExists(this) || !getAllDirectories(true).isEmpty(); } @Override public void accept(@NotNull PsiElementVisitor visitor) { if (visitor instanceof JavaElementVisitor) { ((JavaElementVisitor)visitor).visitPackage(this); } else { visitor.visitElement(this); } } public String toString() { return "PsiPackage:" + getQualifiedName(); } @Override @NotNull public PsiClass[] getClasses() { return getClasses(allScope()); } protected GlobalSearchScope allScope() { return PsiPackageImplementationHelper.getInstance().adjustAllScope(this, GlobalSearchScope.allScope(getProject())); } @Override @NotNull public PsiClass[] getClasses(@NotNull GlobalSearchScope scope) { return getFacade().getClasses(this, scope); } @Override @Nullable public PsiModifierList getAnnotationList() { if (myAnnotationList == null) { myAnnotationList = CachedValuesManager.getManager(myManager.getProject()).createCachedValue(new PackageAnnotationValueProvider()); } return myAnnotationList.getValue(); } @Override @NotNull public PsiPackage[] getSubPackages() { return getSubPackages(allScope()); } @Override @NotNull public PsiPackage[] getSubPackages(@NotNull GlobalSearchScope scope) { return getFacade().getSubPackages(this, scope); } private JavaPsiFacadeImpl getFacade() { return (JavaPsiFacadeImpl)JavaPsiFacade.getInstance(myManager.getProject()); } private Set<String> getClassNamesCache() { SoftReference<Set<String>> ref = myPublicClassNamesCache; Set<String> cache = ref == null ? null : ref.get(); if (cache == null) { GlobalSearchScope scope = allScope(); if (!scope.isForceSearchingInLibrarySources()) { scope = new DelegatingGlobalSearchScope(scope) { @Override public boolean isForceSearchingInLibrarySources() { return true; } }; } cache = getFacade().getClassNames(this, scope); myPublicClassNamesCache = new SoftReference<Set<String>>(cache); } return cache; } @NotNull private PsiClass[] findClassesByName(String name, GlobalSearchScope scope) { final String qName = getQualifiedName(); final String classQName = !qName.isEmpty() ? qName + "." + name : name; return getFacade().findClasses(classQName, scope); } @Override public boolean containsClassNamed(String name) { return getClassNamesCache().contains(name); } @NotNull @Override public PsiClass[] findClassByShortName(@NotNull String name, @NotNull GlobalSearchScope scope) { if (!containsClassNamed(name)) return PsiClass.EMPTY_ARRAY; return getFacade().findClassByShortName(name, this, scope); } @Nullable private PsiPackage findSubPackageByName(String name) { final String qName = getQualifiedName(); final String subpackageQName = qName.isEmpty() ? name : qName + "." + name; return getFacade().findPackage(subpackageQName); } @Override public boolean processDeclarations(@NotNull PsiScopeProcessor processor, @NotNull ResolveState state, PsiElement lastParent, @NotNull PsiElement place) { GlobalSearchScope scope = place.getResolveScope(); processor.handleEvent(PsiScopeProcessor.Event.SET_DECLARATION_HOLDER, this); ElementClassHint classHint = processor.getHint(ElementClassHint.KEY); final JavaPsiFacade facade = getFacade(); final Condition<String> prefixMatcher = processor.getHint(JavaCompletionHints.NAME_FILTER); if (classHint == null || classHint.shouldProcess(ElementClassHint.DeclarationKind.CLASS)) { NameHint nameHint = processor.getHint(NameHint.KEY); if (nameHint != null) { final String shortName = nameHint.getName(state); if (containsClassNamed(shortName) && processClassesByName(processor, state, scope, shortName)) return false; } else if (prefixMatcher != null) { for (String className : getClassNamesCache()) { if (prefixMatcher.value(className)) { if (processClassesByName(processor, state, scope, className)) return false; } } } else { PsiClass[] classes = getClasses(scope); if (!processClasses(processor, state, classes)) return false; } } if (classHint == null || classHint.shouldProcess(ElementClassHint.DeclarationKind.PACKAGE)) { NameHint nameHint = processor.getHint(NameHint.KEY); if (nameHint != null) { PsiPackage aPackage = findSubPackageByName(nameHint.getName(state)); if (aPackage != null) { if (!processor.execute(aPackage, state)) return false; } } else { PsiPackage[] packs = getSubPackages(scope); for (PsiPackage pack : packs) { final String packageName = pack.getName(); if (packageName == null) continue; if (!facade.getNameHelper().isIdentifier(packageName, PsiUtil.getLanguageLevel(this))) { continue; } if (!processor.execute(pack, state)) { return false; } } } } return true; } private boolean processClassesByName(PsiScopeProcessor processor, ResolveState state, GlobalSearchScope scope, String className) { final PsiClass[] classes = findClassesByName(className, scope); return !processClasses(processor, state, classes); } private static boolean processClasses(PsiScopeProcessor processor, ResolveState state, PsiClass[] classes) { for (PsiClass aClass : classes) { if (!processor.execute(aClass, state)) return false; } return true; } @Override public boolean canNavigate() { return isValid(); } @Override public ItemPresentation getPresentation() { return ItemPresentationProviders.getItemPresentation(this); } @Override public void navigate(final boolean requestFocus) { PsiPackageImplementationHelper.getInstance().navigate(this, requestFocus); } private class PackageAnnotationValueProvider implements CachedValueProvider<PsiModifierList> { private final Object[] OOCB_DEPENDENCY = { PsiModificationTracker.OUT_OF_CODE_BLOCK_MODIFICATION_COUNT }; @Override public Result<PsiModifierList> compute() { List<PsiModifierList> list = new ArrayList<PsiModifierList>(); for(PsiDirectory directory: getDirectories()) { PsiFile file = directory.findFile(PACKAGE_INFO_FILE); if (file != null) { PsiPackageStatement stmt = PsiTreeUtil.getChildOfType(file, PsiPackageStatement.class); if (stmt != null) { final PsiModifierList modifierList = stmt.getAnnotationList(); if (modifierList != null) { list.add(modifierList); } } } } final JavaPsiFacade facade = getFacade(); final GlobalSearchScope scope = allScope(); for (PsiClass aClass : facade.findClasses(getQualifiedName() + ".package-info", scope)) { ContainerUtil.addIfNotNull(aClass.getModifierList(), list); } return new Result<PsiModifierList>(list.isEmpty() ? null : new PsiCompositeModifierList(getManager(), list), OOCB_DEPENDENCY); } } @Override @Nullable public PsiModifierList getModifierList() { return getAnnotationList(); } @Override public boolean hasModifierProperty(@NonNls @NotNull final String name) { return false; } @Override public PsiQualifiedNamedElement getContainer() { return getParentPackage(); } }