2 * Copyright 2000-2014 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com.jetbrains.python.psi.resolve;
18 import com.google.common.base.Preconditions;
19 import com.google.common.collect.Lists;
20 import com.google.common.collect.Sets;
21 import com.intellij.facet.Facet;
22 import com.intellij.facet.FacetManager;
23 import com.intellij.openapi.extensions.Extensions;
24 import com.intellij.openapi.module.Module;
25 import com.intellij.openapi.module.ModuleManager;
26 import com.intellij.openapi.project.Project;
27 import com.intellij.openapi.projectRoots.Sdk;
28 import com.intellij.openapi.roots.FileIndexFacade;
29 import com.intellij.openapi.vfs.VirtualFile;
30 import com.intellij.psi.PsiDirectory;
31 import com.intellij.psi.PsiElement;
32 import com.intellij.psi.PsiFile;
33 import com.intellij.psi.PsiNamedElement;
34 import com.intellij.psi.util.PsiTreeUtil;
35 import com.intellij.psi.util.QualifiedName;
36 import com.jetbrains.python.codeInsight.userSkeletons.PyUserSkeletonsUtil;
37 import com.jetbrains.python.console.PydevConsoleRunner;
38 import com.jetbrains.python.facet.PythonPathContributingFacet;
39 import com.jetbrains.python.psi.PyFile;
40 import com.jetbrains.python.psi.impl.PyBuiltinCache;
41 import com.jetbrains.python.psi.impl.PyImportResolver;
42 import com.jetbrains.python.sdk.PySdkUtil;
43 import com.jetbrains.python.sdk.PythonSdkType;
44 import org.jetbrains.annotations.NotNull;
45 import org.jetbrains.annotations.Nullable;
47 import java.util.ArrayList;
48 import java.util.Collections;
49 import java.util.List;
53 * Resolves the specified qualified name in the specified context (module, all modules or a file) to a file or directory.
57 public class QualifiedNameResolverImpl implements RootVisitor, QualifiedNameResolver {
58 boolean myCheckForPackage = true;
59 private final QualifiedNameResolveContext myContext = new QualifiedNameResolveContext();
60 private final @NotNull QualifiedName myQualifiedName;
61 final Set<PsiElement> mySourceResults = Sets.newLinkedHashSet();
62 final Set<PsiElement> myLibResults = Sets.newLinkedHashSet();
63 final Set<PsiElement> myForeignResults = Sets.newLinkedHashSet();
64 private boolean myVisitAllModules = false;
65 private int myRelativeLevel = -1;
66 private boolean myWithoutRoots;
67 private boolean myWithoutForeign;
68 private boolean myWithMembers;
70 public QualifiedNameResolverImpl(@NotNull String qNameString) {
71 myQualifiedName = QualifiedName.fromDottedString(qNameString);
74 public QualifiedNameResolverImpl(@NotNull QualifiedName qName) {
75 myQualifiedName = qName;
79 public QualifiedNameResolver withContext(QualifiedNameResolveContext context) {
80 myContext.copyFrom(context);
85 public QualifiedNameResolver fromElement(@NotNull PsiElement foothold) {
86 myContext.setFromElement(foothold);
87 if (PydevConsoleRunner.isInPydevConsole(foothold)) {
89 Sdk sdk = PydevConsoleRunner.getConsoleSdk(foothold);
91 myContext.setSdk(sdk);
98 public QualifiedNameResolver fromModule(@NotNull Module module) {
99 myContext.setFromModule(module);
104 public QualifiedNameResolver fromSdk(@NotNull Project project, @NotNull Sdk sdk) {
105 myContext.setFromSdk(project, sdk);
109 private boolean isAcceptRootAsTopLevelPackage() {
110 Module module = myContext.getModule();
111 if (module != null) {
112 Facet[] facets = FacetManager.getInstance(module).getAllFacets();
113 for (Facet facet : facets) {
114 if (facet instanceof PythonPathContributingFacet && ((PythonPathContributingFacet)facet).acceptRootAsTopLevelPackage()) {
123 public QualifiedNameResolver withAllModules() {
124 myVisitAllModules = true;
129 * Specifies that we need to look for the name in the specified SDK (instead of the SDK assigned to the module, if any).
131 * @param sdk the SDK in which the name should be searched.
135 public QualifiedNameResolver withSdk(Sdk sdk) {
136 myContext.setSdk(sdk);
141 * Specifies whether we should attempt to resolve imports relative to the current file.
143 * @param relativeLevel if >= 0, we try to resolve at the specified number of levels above the current file.
147 public QualifiedNameResolver withRelative(int relativeLevel) {
148 myRelativeLevel = relativeLevel;
153 * Specifies that we should only try to resolve relative to the current file, not in roots.
158 public QualifiedNameResolver withoutRoots() {
159 myWithoutRoots = true;
164 public QualifiedNameResolver withoutForeign() {
165 myWithoutForeign = true;
170 public QualifiedNameResolver withMembers() {
171 myWithMembers = true;
176 * Specifies that we're looking for a file in a directory hierarchy, not a module in the Python package hierarchy
177 * (so we don't need to check for existence of __init__.py)
182 public QualifiedNameResolver withPlainDirectories() {
183 myCheckForPackage = false;
187 public boolean visitRoot(final VirtualFile root, @Nullable Module module, @Nullable Sdk sdk, boolean isModuleSource) {
188 if (!root.isValid()) {
191 if (root.equals(PyUserSkeletonsUtil.getUserSkeletonsDirectory())) {
194 PsiElement resolveResult = resolveInRoot(root);
195 if (resolveResult != null) {
196 addRoot(resolveResult, isModuleSource);
199 if (isAcceptRootAsTopLevelPackage() && myQualifiedName.matchesPrefix(QualifiedName.fromDottedString(root.getName()))) {
200 resolveResult = resolveInRoot(root.getParent());
201 if (resolveResult != null) {
202 addRoot(resolveResult, isModuleSource);
209 private void addRoot(PsiElement resolveResult, boolean isModuleSource) {
210 if (isModuleSource) {
211 mySourceResults.add(resolveResult);
214 myLibResults.add(resolveResult);
220 public List<PsiElement> resultsAsList() {
221 if (!myContext.isValid()) {
222 return Collections.emptyList();
225 final PsiFile footholdFile = myContext.getFootholdFile();
226 if (myRelativeLevel >= 0 && footholdFile != null && !PyUserSkeletonsUtil.isUnderUserSkeletonsDirectory(footholdFile)) {
227 PsiDirectory dir = footholdFile.getContainingDirectory();
228 if (myRelativeLevel > 0) {
229 dir = ResolveImportUtil.stepBackFrom(footholdFile, myRelativeLevel);
232 PsiElement module = resolveModuleAt(dir);
233 if (module != null) {
234 addRoot(module, true);
238 final PythonPathCache cache = findMyCache();
239 final boolean mayCache = cache != null && !myWithoutRoots && !myWithoutForeign;
241 final List<PsiElement> cachedResults = cache.get(myQualifiedName);
242 if (cachedResults != null) {
243 mySourceResults.addAll(cachedResults);
244 return Lists.newArrayList(mySourceResults);
248 if (!myWithoutRoots) {
249 addResultsFromRoots();
251 else if (footholdFile != null){
252 addResultsFromSkeletons(footholdFile);
255 mySourceResults.addAll(myLibResults);
256 myLibResults.clear();
258 if (!myWithoutForeign) {
259 for (PyImportResolver resolver : Extensions.getExtensions(PyImportResolver.EP_NAME)) {
260 PsiElement foreign = resolver.resolveImportReference(myQualifiedName, myContext);
261 if (foreign != null) {
262 myForeignResults.add(foreign);
265 mySourceResults.addAll(myForeignResults);
266 myForeignResults.clear();
269 final ArrayList<PsiElement> results = Lists.newArrayList(mySourceResults);
271 cache.put(myQualifiedName, results);
277 * Resolve relative imports from sdk root to the skeleton dir
279 private void addResultsFromSkeletons(@NotNull final PsiFile foothold) {
280 final boolean inSource = FileIndexFacade.getInstance(foothold.getProject()).isInContent(foothold.getVirtualFile());
281 if (inSource) return;
282 PsiDirectory containingDirectory = foothold.getContainingDirectory();
283 if (myRelativeLevel > 0) {
284 containingDirectory = ResolveImportUtil.stepBackFrom(foothold, myRelativeLevel);
286 if (containingDirectory != null) {
287 final QualifiedName containingPath = QualifiedNameFinder.findCanonicalImportPath(containingDirectory, null);
288 if (containingPath != null && containingPath.getComponentCount() > 0) {
289 final QualifiedName absolutePath = containingPath.append(myQualifiedName.toString());
290 final QualifiedNameResolverImpl absoluteVisitor =
291 (QualifiedNameResolverImpl)new QualifiedNameResolverImpl(absolutePath).fromElement(foothold);
293 final Sdk sdk = PythonSdkType.getSdk(foothold);
294 if (sdk == null) return;
295 final VirtualFile skeletonsDir = PySdkUtil.findSkeletonsDir(sdk);
296 if (skeletonsDir == null) return;
297 final PsiDirectory directory = myContext.getPsiManager().findDirectory(skeletonsDir);
298 final PsiElement psiElement = absoluteVisitor.resolveModuleAt(directory);
299 if (psiElement != null)
300 myLibResults.add(psiElement);
305 private void addResultsFromRoots() {
306 if (myVisitAllModules) {
307 for (Module mod : ModuleManager.getInstance(myContext.getProject()).getModules()) {
308 RootVisitorHost.visitRoots(mod, true, this);
311 if (myContext.getSdk() != null) {
312 RootVisitorHost.visitSdkRoots(myContext.getSdk(), this);
314 else if (myContext.getFootholdFile() != null) {
315 RootVisitorHost.visitSdkRoots(myContext.getFootholdFile(), this);
318 else if (myContext.getModule() != null) {
319 final boolean otherSdk = withOtherSdk();
320 RootVisitorHost.visitRoots(myContext.getModule(), otherSdk, this);
322 RootVisitorHost.visitSdkRoots(myContext.getSdk(), this);
325 else if (myContext.getFootholdFile() != null) {
326 RootVisitorHost.visitSdkRoots(myContext.getFootholdFile(), this);
328 else if (myContext.getSdk() != null) {
329 RootVisitorHost.visitSdkRoots(myContext.getSdk(), this);
332 throw new IllegalStateException();
338 public PsiElement firstResult() {
339 final List<PsiElement> results = resultsAsList();
340 return results.size() > 0 ? results.get(0) : null;
345 public <T extends PsiElement> List<T> resultsOfType(Class<T> clazz) {
346 List<T> result = new ArrayList<T>();
347 for (PsiElement element : resultsAsList()) {
348 if (clazz.isInstance(element)) {
349 //noinspection unchecked
350 result.add((T)element);
358 public <T extends PsiElement> T firstResultOfType(Class<T> clazz) {
359 final List<T> list = resultsOfType(clazz);
360 return list.size() > 0 ? list.get(0) : null;
363 private boolean withOtherSdk() {
364 return myContext.getSdk() != null && myContext.getSdk() != PythonSdkType.findPythonSdk(myContext.getModule());
368 private PythonPathCache findMyCache() {
369 if (myVisitAllModules) {
372 if (myContext.getModule() != null) {
373 return withOtherSdk() ? null : PythonModulePathCache.getInstance(myContext.getModule());
375 if (myContext.getFootholdFile() != null) {
376 final Sdk sdk = PyBuiltinCache.findSdkForNonModuleFile(myContext.getFootholdFile());
378 return PythonSdkPathCache.getInstance(myContext.getProject(), sdk);
385 private PsiElement resolveInRoot(VirtualFile root) {
386 if (!root.isDirectory()) {
387 // if we have added a file as a root, it's unlikely that we'll be able to resolve anything under it in 'files only' resolve mode
390 return resolveModuleAt(myContext.getPsiManager().findDirectory(root));
394 * Searches for a module at given directory, unwinding qualifiers and traversing directories as needed.
396 * @param directory where to start from; top qualifier will be searched for here.
399 public PsiElement resolveModuleAt(@Nullable PsiDirectory directory) {
401 if (directory == null || !directory.isValid()) return null;
403 PsiElement seeker = directory;
404 for (String name : myQualifiedName.getComponents()) {
408 seeker = ResolveImportUtil.resolveChild(seeker, name, myContext.getFootholdFile(), !myWithMembers, myCheckForPackage);
414 public Module getModule() {
415 return myContext.getModule();
420 public <T extends PsiNamedElement> T resolveTopLevelMember(@NotNull final Class<T> aClass) {
421 Preconditions.checkState(getModule() != null, "Module is not set");
422 final String memberName = myQualifiedName.getLastComponent();
423 if (memberName == null) {
427 new QualifiedNameResolverImpl(myQualifiedName.removeLastComponent()).fromModule(getModule()).firstResultOfType(PyFile.class);
431 for (final T element : PsiTreeUtil.getChildrenOfTypeAsList(file, aClass)) {
432 if (memberName.equals(element.getName())) {