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