042dbf6bd1fc87f3dc4928803d195244f6112e47
[idea/community.git] / platform / lang-impl / src / com / intellij / psi / PsiAnchor.java
1 /*
2  * Copyright 2000-2009 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;
18
19 import com.intellij.lang.ASTNode;
20 import com.intellij.lang.Language;
21 import com.intellij.openapi.application.ApplicationManager;
22 import com.intellij.openapi.project.Project;
23 import com.intellij.openapi.util.Comparing;
24 import com.intellij.openapi.util.NullableComputable;
25 import com.intellij.openapi.util.TextRange;
26 import com.intellij.openapi.vfs.VirtualFile;
27 import com.intellij.psi.impl.light.LightElement;
28 import com.intellij.psi.impl.smartPointers.SelfElementInfo;
29 import com.intellij.psi.impl.source.PsiFileImpl;
30 import com.intellij.psi.impl.source.PsiFileWithStubSupport;
31 import com.intellij.psi.stubs.IStubElementType;
32 import com.intellij.psi.stubs.StubBase;
33 import com.intellij.psi.stubs.StubElement;
34 import com.intellij.psi.stubs.StubTree;
35 import com.intellij.psi.tree.IStubFileElementType;
36 import com.intellij.psi.util.PsiTreeUtil;
37 import org.jetbrains.annotations.NonNls;
38 import org.jetbrains.annotations.NotNull;
39 import org.jetbrains.annotations.Nullable;
40
41 import java.util.List;
42 import java.util.Set;
43
44 /**
45  * @author db
46  */
47 public abstract class PsiAnchor {
48   @Nullable
49   public abstract PsiElement retrieve();
50   public abstract PsiFile getFile();
51   public abstract int getStartOffset();
52   public abstract int getEndOffset();
53
54   public static PsiAnchor create(@NotNull final PsiElement element) {
55     if (!element.isValid()) {
56       throw new PsiInvalidElementAccessException(element);
57     }
58
59     if (element instanceof PsiFile) {
60       VirtualFile virtualFile = ((PsiFile)element).getVirtualFile();
61       if (virtualFile != null) return new PsiFileReference(virtualFile, element.getProject());
62       return new HardReference(element);
63     }
64     if (element instanceof PsiDirectory) {
65       VirtualFile virtualFile = ((PsiDirectory)element).getVirtualFile();
66       return new PsiDirectoryReference(virtualFile, element.getProject());
67     }
68
69     PsiFile file = element.getContainingFile();
70     if (file == null) {
71       return new HardReference(element);
72     }
73     VirtualFile virtualFile = file.getVirtualFile();
74     if (virtualFile == null) return new HardReference(element);
75
76     PsiAnchor stubRef = createStubReference(element, file);
77     if (stubRef != null) return stubRef;
78
79     if (!element.isPhysical() && element instanceof PsiCompiledElement|| element instanceof LightElement) {
80       return new HardReference(element);
81     }
82
83     TextRange textRange = element.getTextRange();
84     if (textRange == null) {
85       return new HardReference(element);
86     }
87
88     Language lang = null;
89     final FileViewProvider viewProvider = file.getViewProvider();
90     final Set<Language> languages = viewProvider.getLanguages();
91     for (Language l : languages) {
92       if (PsiTreeUtil.isAncestor(viewProvider.getPsi(l), element, false)) {
93         lang = l;
94         break;
95       }
96     }
97
98     if (lang == null) lang = element.getLanguage();
99     return new TreeRangeReference(file, textRange.getStartOffset(), textRange.getEndOffset(), element.getClass(), lang, virtualFile);
100   }
101
102   @Nullable
103   public static StubIndexReference createStubReference(@NotNull PsiElement element, @NotNull PsiFile containingFile) {
104     if (element instanceof StubBasedPsiElement &&
105         element.isPhysical() &&
106         (element instanceof PsiCompiledElement || ((PsiFileImpl)containingFile).getContentElementType() instanceof IStubFileElementType)) {
107       final StubBasedPsiElement elt = (StubBasedPsiElement)element;
108       final IStubElementType elementType = elt.getElementType();
109       if (elt.getStub() != null || elementType.shouldCreateStub(element.getNode())) {
110         int index = calcStubIndex((StubBasedPsiElement)element);
111         if (index != -1) {
112           return new StubIndexReference(containingFile, index, containingFile.getLanguage(), elementType);
113         }
114       }
115     }
116     return null;
117   }
118
119   public static int calcStubIndex(StubBasedPsiElement psi) {
120     if (psi instanceof PsiFile) {
121       return 0;
122     }
123
124     final StubElement liveStub = psi.getStub();
125     if (liveStub != null) {
126       return ((StubBase)liveStub).id;
127     }
128
129     PsiFileImpl file = (PsiFileImpl)psi.getContainingFile();
130     final StubTree stubTree = file.calcStubTree();
131     for (StubElement<?> stb : stubTree.getPlainList()) {
132       if (stb.getPsi() == psi) {
133         return ((StubBase)stb).id;
134       }
135     }
136
137     return -1; // it is possible via custom stub builder intentionally not producing stubs for stubbed elements
138   }
139
140   private static class TreeRangeReference extends PsiAnchor {
141     private final VirtualFile myVirtualFile;
142     private final Project myProject;
143     private final Language myLanguage;
144     private final Language myFileLanguage;
145     private final int myStartOffset;
146     private final int myEndOffset;
147     private final Class myClass;
148
149     private TreeRangeReference(@NotNull PsiFile file,
150                                int startOffset,
151                                int endOffset,
152                                @NotNull Class aClass,
153                                @NotNull Language language,
154                                @NotNull VirtualFile virtualFile) {
155       myVirtualFile = virtualFile;
156       myProject = file.getProject();
157       myStartOffset = startOffset;
158       myEndOffset = endOffset;
159       myClass = aClass;
160       myLanguage = language;
161       myFileLanguage = file.getLanguage();
162     }
163
164     @Override
165     @Nullable
166     public PsiElement retrieve() {
167       PsiFile psiFile = getFile();
168       if (psiFile == null || !psiFile.isValid()) return null;
169       PsiElement element = psiFile.getViewProvider().findElementAt(myStartOffset, myLanguage);
170       if (element == null) return null;
171
172       while  (!element.getClass().equals(myClass) ||
173               element.getTextRange().getStartOffset() != myStartOffset ||
174               element.getTextRange().getEndOffset() != myEndOffset) {
175         element = element.getParent();
176         if (element == null || element.getTextRange() == null) return null;
177       }
178
179       return element;
180     }
181
182     @Override
183     @Nullable
184     public PsiFile getFile() {
185       return SelfElementInfo.restoreFileFromVirtual(myVirtualFile, myProject, myLanguage);
186     }
187
188     @Override
189     public int getStartOffset() {
190       return myStartOffset;
191     }
192
193     @Override
194     public int getEndOffset() {
195       return myEndOffset;
196     }
197
198     public boolean equals(Object o) {
199       if (this == o) return true;
200       if (!(o instanceof TreeRangeReference)) return false;
201
202       final TreeRangeReference that = (TreeRangeReference)o;
203
204       return myEndOffset == that.myEndOffset &&
205              myStartOffset == that.myStartOffset &&
206              myClass.equals(that.myClass) &&
207              myVirtualFile.equals(that.myVirtualFile);
208     }
209
210     public int hashCode() {
211       int result = myClass.getName().hashCode();
212       result = 31 * result + myStartOffset;
213       result = 31 * result + myEndOffset;
214       result = 31 * result + myVirtualFile.hashCode();
215
216       return result;
217     }
218   }
219
220   private static class HardReference extends PsiAnchor {
221     private final PsiElement myElement;
222
223     private HardReference(final PsiElement element) {
224       myElement = element;
225     }
226
227     @Override
228     public PsiElement retrieve() {
229       return myElement;
230     }
231
232     @Override
233     public PsiFile getFile() {
234       return myElement.getContainingFile();
235     }
236
237     @Override
238     public int getStartOffset() {
239       return myElement.getTextRange().getStartOffset();
240     }
241
242     @Override
243     public int getEndOffset() {
244       return myElement.getTextRange().getEndOffset();
245     }
246
247
248     public boolean equals(final Object o) {
249       if (this == o) return true;
250       if (!(o instanceof HardReference)) return false;
251
252       final HardReference that = (HardReference)o;
253
254       return myElement.equals(that.myElement);
255     }
256
257     public int hashCode() {
258       return myElement.hashCode();
259     }
260   }
261
262   private static class PsiFileReference extends PsiAnchor {
263     protected final VirtualFile myFile;
264     protected final Project myProject;
265
266     private PsiFileReference(@NotNull VirtualFile file, @NotNull Project project) {
267       myFile = file;
268       myProject = project;
269     }
270
271     @Override
272     public PsiElement retrieve() {
273       return getFile();
274     }
275
276     @Override
277     public PsiFile getFile() {
278       return SelfElementInfo.restoreFileFromVirtual(myFile, myProject);
279     }
280
281     @Override
282     public int getStartOffset() {
283       return 0;
284     }
285
286     @Override
287     public int getEndOffset() {
288       return (int)myFile.getLength();
289     }
290
291     public boolean equals(final Object o) {
292       if (this == o) return true;
293       if (!(o instanceof PsiFileReference)) return false;
294
295       final PsiFileReference that = (PsiFileReference)o;
296
297       return myFile.equals(that.myFile);
298     }
299
300     public int hashCode() {
301       return myFile.hashCode();
302     }
303   }
304   private static class PsiDirectoryReference extends PsiFileReference {
305     private PsiDirectoryReference(@NotNull VirtualFile file, @NotNull Project project) {
306       super(file, project);
307       assert file.isDirectory() : file;
308     }
309
310     @Override
311     public PsiElement retrieve() {
312       return SelfElementInfo.restoreDirectoryFromVirtual(myFile, myProject);
313     }
314
315     @Override
316     public PsiFile getFile() {
317       return null;
318     }
319
320     @Override
321     public int getEndOffset() {
322       return -1;
323     }
324   }
325
326   public static PsiElement restoreFromStubIndex(PsiFileWithStubSupport fileImpl,
327                                                 int index,
328                                                 IStubElementType elementType) {
329     if (fileImpl == null) return null;
330     StubTree tree = fileImpl.getStubTree();
331
332     boolean foreign = tree == null;
333     if (foreign) {
334       if (fileImpl instanceof PsiFileImpl) {
335         // Note: as far as this is a realization of StubIndexReference fileImpl#getContentElementType() must be instance of IStubFileElementType
336         tree = ((PsiFileImpl)fileImpl).calcStubTree();
337       }
338       else {
339         return null;
340       }
341     }
342
343     List<StubElement<?>> list = tree.getPlainList();
344     if (index >= list.size()) return null;
345     StubElement stub = list.get(index);
346
347     if (stub.getStubType() != elementType) return null;
348
349     if (foreign) {
350       final PsiElement cachedPsi = ((StubBase)stub).getCachedPsi();
351       if (cachedPsi != null) return cachedPsi;
352
353       final ASTNode ast = fileImpl.findTreeForStub(tree, stub);
354       return ast != null ? ast.getPsi() : null;
355     }
356     else {
357       return stub.getPsi();
358     }
359   }
360
361   public static class StubIndexReference extends PsiAnchor {
362     private final VirtualFile myVirtualFile;
363     private final Project myProject;
364     private final int myIndex;
365     private final Language myLanguage;
366     private final IStubElementType myElementType;
367
368     public StubIndexReference(@NotNull final PsiFile file, final int index, @NotNull Language language, IStubElementType elementType) {
369       myLanguage = language;
370       myElementType = elementType;
371       myVirtualFile = file.getVirtualFile();
372       myProject = file.getProject();
373       myIndex = index;
374     }
375
376     @Override
377     @Nullable
378     public PsiFile getFile() {
379       if (myProject.isDisposed() || !myVirtualFile.isValid()) {
380         return null;
381       }
382       final PsiFile file = PsiManager.getInstance(myProject).findFile(myVirtualFile);
383       if (file == null) {
384         return null;
385       }
386       if (file.getLanguage() == myLanguage) {
387         return file;
388       }
389       return file.getViewProvider().getPsi(myLanguage);
390     }
391
392     @Override
393     public PsiElement retrieve() {
394       return ApplicationManager.getApplication().runReadAction(new NullableComputable<PsiElement>() {
395         @Override
396         public PsiElement compute() {
397           return restoreFromStubIndex((PsiFileWithStubSupport)getFile(), myIndex, myElementType);
398         }
399       });
400     }
401
402     @Override
403     public boolean equals(final Object o) {
404       if (this == o) return true;
405       if (!(o instanceof StubIndexReference)) return false;
406
407       final StubIndexReference that = (StubIndexReference)o;
408
409       return myIndex == that.myIndex &&
410              myVirtualFile.equals(that.myVirtualFile) &&
411              Comparing.equal(myElementType, that.myElementType) &&
412              myLanguage == that.myLanguage;
413     }
414
415     @Override
416     public int hashCode() {
417       return ((31 * myVirtualFile.hashCode() + myIndex) * 31 + (myElementType == null ? 0 : myElementType.hashCode())) * 31 + myLanguage.hashCode();
418     }
419
420     @NonNls
421     @Override
422     public String toString() {
423       return "StubIndexReference{" +
424              "myVirtualFile=" + myVirtualFile +
425              ", myProject=" + myProject +
426              ", myIndex=" + myIndex +
427              ", myLanguage=" + myLanguage +
428              ", myElementType=" + myElementType +
429              '}';
430     }
431
432     @Override
433     public int getStartOffset() {
434       final PsiElement resolved = retrieve();
435       if (resolved == null) throw new PsiInvalidElementAccessException(null);
436       return resolved.getTextRange().getStartOffset();
437     }
438
439     @Override
440     public int getEndOffset() {
441       final PsiElement resolved = retrieve();
442       if (resolved == null) throw new PsiInvalidElementAccessException(null);
443       return resolved.getTextRange().getEndOffset();
444     }
445
446     public VirtualFile getVirtualFile() {
447       return myVirtualFile;
448     }
449
450     public Project getProject() {
451       return myProject;
452     }
453   }
454 }
455