use file's directory as context root when resolving references in FileIncludeProvider...
[idea/community.git] / platform / lang-impl / src / com / intellij / psi / impl / include / FileIncludeManagerImpl.java
1 /*
2  * Copyright 2000-2013 JetBrains s.r.o.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16
17 package com.intellij.psi.impl.include;
18
19 import com.intellij.openapi.extensions.Extensions;
20 import com.intellij.openapi.fileTypes.FileTypes;
21 import com.intellij.openapi.project.Project;
22 import com.intellij.openapi.util.Key;
23 import com.intellij.openapi.util.Pair;
24 import com.intellij.openapi.vfs.VfsUtilCore;
25 import com.intellij.openapi.vfs.VirtualFile;
26 import com.intellij.openapi.vfs.VirtualFileManager;
27 import com.intellij.openapi.vfs.VirtualFileWithId;
28 import com.intellij.psi.PsiFile;
29 import com.intellij.psi.PsiFileFactory;
30 import com.intellij.psi.PsiFileSystemItem;
31 import com.intellij.psi.PsiManager;
32 import com.intellij.psi.impl.source.PsiFileImpl;
33 import com.intellij.psi.impl.source.resolve.reference.impl.providers.FileReferenceHelper;
34 import com.intellij.psi.impl.source.resolve.reference.impl.providers.FileReferenceSet;
35 import com.intellij.psi.search.GlobalSearchScope;
36 import com.intellij.psi.util.CachedValueProvider;
37 import com.intellij.psi.util.CachedValuesManager;
38 import com.intellij.psi.util.ParameterizedCachedValue;
39 import com.intellij.psi.util.ParameterizedCachedValueProvider;
40 import com.intellij.util.Processor;
41 import com.intellij.util.containers.ContainerUtil;
42 import com.intellij.util.containers.HashMap;
43 import com.intellij.util.containers.MultiMap;
44 import gnu.trove.THashSet;
45 import org.jetbrains.annotations.NotNull;
46 import org.jetbrains.annotations.Nullable;
47
48 import java.util.*;
49
50 /**
51  * @author Dmitry Avdeev
52  */
53 public class FileIncludeManagerImpl extends FileIncludeManager {
54
55   private final Project myProject;
56   private final PsiManager myPsiManager;
57   private final PsiFileFactory myPsiFileFactory;
58   private final CachedValuesManager myCachedValuesManager;
59
60   private final IncludeCacheHolder myIncludedHolder = new IncludeCacheHolder("compile time includes", "runtime includes") {
61     @Override
62     protected VirtualFile[] computeFiles(final PsiFile file, final boolean compileTimeOnly) {
63       final Set<VirtualFile> files = new THashSet<VirtualFile>();
64       processIncludes(file, new Processor<FileIncludeInfo>() {
65         @Override
66         public boolean process(FileIncludeInfo info) {
67           if (compileTimeOnly != info.runtimeOnly) {
68             PsiFileSystemItem item = resolveFileInclude(info, file);
69             if (item != null) {
70               ContainerUtil.addIfNotNull(files, item.getVirtualFile());
71             }
72           }
73           return true;
74         }
75
76       });
77       return VfsUtilCore.toVirtualFileArray(files);
78     }
79   };
80   private final Map<String, FileIncludeProvider> myProviderMap;
81
82   public void processIncludes(PsiFile file, Processor<FileIncludeInfo> processor) {
83     GlobalSearchScope scope = GlobalSearchScope.allScope(myProject);
84     List<FileIncludeInfoImpl> infoList = FileIncludeIndex.getIncludes(file.getVirtualFile(), scope);
85     for (FileIncludeInfoImpl info : infoList) {
86       if (!processor.process(info)) {
87         return;
88       }
89     }
90   }
91
92   private final IncludeCacheHolder myIncludingHolder = new IncludeCacheHolder("compile time contexts", "runtime contexts") {
93     @Override
94     protected VirtualFile[] computeFiles(PsiFile context, boolean compileTimeOnly) {
95       final Set<VirtualFile> files = new THashSet<VirtualFile>();
96       processIncludingFiles(context, new Processor<Pair<VirtualFile, FileIncludeInfo>>() {
97         @Override
98         public boolean process(Pair<VirtualFile, FileIncludeInfo> virtualFileFileIncludeInfoPair) {
99           files.add(virtualFileFileIncludeInfoPair.first);
100           return true;
101         }
102       });
103       return VfsUtilCore.toVirtualFileArray(files);
104     }
105   };
106
107   @Override
108   public void processIncludingFiles(PsiFile context, Processor<Pair<VirtualFile, FileIncludeInfo>> processor) {
109     context = context.getOriginalFile();
110     VirtualFile contextFile = context.getVirtualFile();
111     if (contextFile == null) return;
112     MultiMap<VirtualFile,FileIncludeInfoImpl> infoList = FileIncludeIndex.getIncludingFileCandidates(context.getName(), GlobalSearchScope.allScope(myProject));
113     for (VirtualFile candidate : infoList.keySet()) {
114       PsiFile psiFile = myPsiManager.findFile(candidate);
115       if (psiFile == null || context.equals(psiFile)) continue;
116       for (FileIncludeInfo info : infoList.get(candidate)) {
117         PsiFileSystemItem item = resolveFileInclude(info, psiFile);
118         if (item != null && contextFile.equals(item.getVirtualFile())) {
119           if (!processor.process(Pair.create(candidate, info))) {
120             return;
121           }
122         }
123       }
124     }
125   }
126
127   public FileIncludeManagerImpl(Project project, PsiManager psiManager, PsiFileFactory psiFileFactory,
128                                 CachedValuesManager cachedValuesManager) {
129     myProject = project;
130     myPsiManager = psiManager;
131     myPsiFileFactory = psiFileFactory;
132
133     FileIncludeProvider[] providers = Extensions.getExtensions(FileIncludeProvider.EP_NAME);
134     myProviderMap = new HashMap<String, FileIncludeProvider>(providers.length);
135     for (FileIncludeProvider provider : providers) {
136       FileIncludeProvider old = myProviderMap.put(provider.getId(), provider);
137       assert old == null;
138     }
139     myCachedValuesManager = cachedValuesManager;
140   }
141
142   @Override
143   public VirtualFile[] getIncludedFiles(@NotNull VirtualFile file, boolean compileTimeOnly) {
144     return getIncludedFiles(file, compileTimeOnly, false);
145   }
146
147   @Override
148   public VirtualFile[] getIncludedFiles(@NotNull VirtualFile file, boolean compileTimeOnly, boolean recursively) {
149     if (file instanceof VirtualFileWithId) {
150       return myIncludedHolder.getAllFiles(file, compileTimeOnly, recursively);
151     }
152     else {
153       return VirtualFile.EMPTY_ARRAY;
154     }
155   }
156
157   @Override
158   public VirtualFile[] getIncludingFiles(@NotNull VirtualFile file, boolean compileTimeOnly) {
159     return myIncludingHolder.getAllFiles(file, compileTimeOnly, false);
160   }
161
162   @Override
163   public PsiFileSystemItem resolveFileInclude(@NotNull final FileIncludeInfo info, @NotNull final PsiFile context) {
164     return doResolve(info, context);
165   }
166
167   @Nullable
168   private PsiFileSystemItem doResolve(@NotNull final FileIncludeInfo info, @NotNull final PsiFile context) {
169     if (info instanceof FileIncludeInfoImpl) {
170       String id = ((FileIncludeInfoImpl)info).providerId;
171       FileIncludeProvider provider = id == null ? null : myProviderMap.get(id);
172       final PsiFileSystemItem resolvedByProvider = provider == null ? null : provider.resolveIncludedFile(info, context);
173       if (resolvedByProvider != null) {
174         return resolvedByProvider;
175       }
176     }
177
178     PsiFileImpl psiFile = (PsiFileImpl)myPsiFileFactory.createFileFromText("dummy.txt", FileTypes.PLAIN_TEXT, info.path);
179     psiFile.setOriginalFile(context);
180     return new FileReferenceSet(psiFile) {
181       @Override
182       protected boolean useIncludingFileAsContext() {
183         return false;
184       }
185
186       @NotNull
187       @Override
188       public Collection<PsiFileSystemItem> computeDefaultContexts() {
189         Collection<PsiFileSystemItem> contexts = super.computeDefaultContexts();
190         if (!isAbsolutePathReference()) {
191           return addFileDirectoryToContexts(contexts, context);
192         }
193         return contexts;
194       }
195     }.resolve();
196   }
197
198   @NotNull
199   private static Collection<PsiFileSystemItem> addFileDirectoryToContexts(@NotNull Collection<PsiFileSystemItem> contexts,
200                                                                           @NotNull PsiFile context) {
201     VirtualFile file = context.getOriginalFile().getVirtualFile();
202     VirtualFile dir = file == null ? null : file.getParent();
203     if (dir != null) {
204       PsiFileSystemItem item = FileReferenceHelper.getPsiFileSystemItem(context.getManager(), dir);
205       if (item != null && !contexts.contains(item)) {
206         List<PsiFileSystemItem> result = ContainerUtil.newArrayList(contexts);
207         result.add(item);
208         return result;
209       }
210     }
211     return contexts;
212   }
213
214   private abstract class IncludeCacheHolder {
215
216     private final Key<ParameterizedCachedValue<VirtualFile[], PsiFile>> COMPILE_TIME_KEY;
217     private final Key<ParameterizedCachedValue<VirtualFile[], PsiFile>> RUNTIME_KEY;
218
219     private final ParameterizedCachedValueProvider<VirtualFile[], PsiFile> COMPILE_TIME_PROVIDER = new IncludedFilesProvider(true) {
220       @Override
221       protected VirtualFile[] computeFiles(PsiFile file, boolean compileTimeOnly) {
222         return IncludeCacheHolder.this.computeFiles(file, compileTimeOnly);
223       }
224     };
225
226     private final ParameterizedCachedValueProvider<VirtualFile[], PsiFile> RUNTIME_PROVIDER = new IncludedFilesProvider(false) {
227       @Override
228       protected VirtualFile[] computeFiles(PsiFile file, boolean compileTimeOnly) {
229         return IncludeCacheHolder.this.computeFiles(file, compileTimeOnly);
230       }
231     };
232
233     private IncludeCacheHolder(String compileTimeKey, String runtimeKey) {
234       COMPILE_TIME_KEY = Key.create(compileTimeKey);
235       RUNTIME_KEY = Key.create(runtimeKey);
236     }
237
238     @NotNull
239     private VirtualFile[] getAllFiles(@NotNull VirtualFile file, boolean compileTimeOnly, boolean recursively) {
240       if (recursively) {
241         Set<VirtualFile> result = new HashSet<VirtualFile>();
242         getAllFilesRecursively(file, compileTimeOnly, result);
243         return VfsUtilCore.toVirtualFileArray(result);
244       }
245       return getFiles(file, compileTimeOnly);
246     }
247
248     private void getAllFilesRecursively(@NotNull VirtualFile file, boolean compileTimeOnly, Set<VirtualFile> result) {
249       if (!result.add(file)) return;
250       VirtualFile[] includes = getFiles(file, compileTimeOnly);
251       if (includes.length != 0) {
252         for (VirtualFile include : includes) {
253           getAllFilesRecursively(include, compileTimeOnly, result);
254         }
255       }
256     }
257
258     private VirtualFile[] getFiles(@NotNull VirtualFile file, boolean compileTimeOnly) {
259       PsiFile psiFile = myPsiManager.findFile(file);
260       if (psiFile == null) {
261         return VirtualFile.EMPTY_ARRAY;
262       }
263       if (compileTimeOnly) {
264         return myCachedValuesManager.getParameterizedCachedValue(psiFile, COMPILE_TIME_KEY, COMPILE_TIME_PROVIDER, false, psiFile);
265       }
266       return myCachedValuesManager.getParameterizedCachedValue(psiFile, RUNTIME_KEY, RUNTIME_PROVIDER, false, psiFile);
267     }
268
269     protected abstract VirtualFile[] computeFiles(PsiFile file, boolean compileTimeOnly);
270
271   }
272
273   private abstract static class IncludedFilesProvider implements ParameterizedCachedValueProvider<VirtualFile[], PsiFile> {
274     private final boolean myRuntimeOnly;
275
276     public IncludedFilesProvider(boolean runtimeOnly) {
277       myRuntimeOnly = runtimeOnly;
278     }
279
280     protected abstract VirtualFile[] computeFiles(PsiFile file, boolean compileTimeOnly);
281
282     @Override
283     public CachedValueProvider.Result<VirtualFile[]> compute(PsiFile psiFile) {
284       VirtualFile[] value = computeFiles(psiFile, myRuntimeOnly);
285       // todo: we need "url modification tracker" for VirtualFile
286       List<Object> deps = new ArrayList<Object>(Arrays.asList(value));
287       deps.add(psiFile);
288       deps.add(VirtualFileManager.getInstance());
289
290       return CachedValueProvider.Result.create(value, deps);
291     }
292   }
293 }