c3a4289e08e931c75a67188a04ed4fdd61f6abb0
[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 int myStartOffset;
145     private final int myEndOffset;
146     private final Class myClass;
147
148     private TreeRangeReference(@NotNull PsiFile file,
149                                int startOffset,
150                                int endOffset,
151                                @NotNull Class aClass,
152                                @NotNull Language language,
153                                @NotNull VirtualFile virtualFile) {
154       myVirtualFile = virtualFile;
155       myProject = file.getProject();
156       myStartOffset = startOffset;
157       myEndOffset = endOffset;
158       myClass = aClass;
159       myLanguage = language;
160     }
161
162     @Override
163     @Nullable
164     public PsiElement retrieve() {
165       PsiFile psiFile = getFile();
166       if (psiFile == null || !psiFile.isValid()) return null;
167       PsiElement element = psiFile.getViewProvider().findElementAt(myStartOffset, myLanguage);
168       if (element == null) return null;
169
170       while  (!element.getClass().equals(myClass) ||
171               element.getTextRange().getStartOffset() != myStartOffset ||
172               element.getTextRange().getEndOffset() != myEndOffset) {
173         element = element.getParent();
174         if (element == null || element.getTextRange() == null) return null;
175       }
176
177       return element;
178     }
179
180     @Override
181     public PsiFile getFile() {
182       return SelfElementInfo.restoreFileFromVirtual(myVirtualFile, myProject);
183     }
184
185     @Override
186     public int getStartOffset() {
187       return myStartOffset;
188     }
189
190     @Override
191     public int getEndOffset() {
192       return myEndOffset;
193     }
194
195     public boolean equals(Object o) {
196       if (this == o) return true;
197       if (!(o instanceof TreeRangeReference)) return false;
198
199       final TreeRangeReference that = (TreeRangeReference)o;
200
201       return myEndOffset == that.myEndOffset &&
202              myStartOffset == that.myStartOffset &&
203              myClass.equals(that.myClass) &&
204              myVirtualFile.equals(that.myVirtualFile);
205     }
206
207     public int hashCode() {
208       int result = myClass.getName().hashCode();
209       result = 31 * result + myStartOffset;
210       result = 31 * result + myEndOffset;
211       result = 31 * result + myVirtualFile.hashCode();
212
213       return result;
214     }
215   }
216
217   private static class HardReference extends PsiAnchor {
218     private final PsiElement myElement;
219
220     private HardReference(final PsiElement element) {
221       myElement = element;
222     }
223
224     @Override
225     public PsiElement retrieve() {
226       return myElement;
227     }
228
229     @Override
230     public PsiFile getFile() {
231       return myElement.getContainingFile();
232     }
233
234     @Override
235     public int getStartOffset() {
236       return myElement.getTextRange().getStartOffset();
237     }
238
239     @Override
240     public int getEndOffset() {
241       return myElement.getTextRange().getEndOffset();
242     }
243
244
245     public boolean equals(final Object o) {
246       if (this == o) return true;
247       if (!(o instanceof HardReference)) return false;
248
249       final HardReference that = (HardReference)o;
250
251       return myElement.equals(that.myElement);
252     }
253
254     public int hashCode() {
255       return myElement.hashCode();
256     }
257   }
258
259   private static class PsiFileReference extends PsiAnchor {
260     protected final VirtualFile myFile;
261     protected final Project myProject;
262
263     private PsiFileReference(@NotNull VirtualFile file, @NotNull Project project) {
264       myFile = file;
265       myProject = project;
266     }
267
268     @Override
269     public PsiElement retrieve() {
270       return getFile();
271     }
272
273     @Override
274     public PsiFile getFile() {
275       return SelfElementInfo.restoreFileFromVirtual(myFile, myProject);
276     }
277
278     @Override
279     public int getStartOffset() {
280       return 0;
281     }
282
283     @Override
284     public int getEndOffset() {
285       return (int)myFile.getLength();
286     }
287
288     public boolean equals(final Object o) {
289       if (this == o) return true;
290       if (!(o instanceof PsiFileReference)) return false;
291
292       final PsiFileReference that = (PsiFileReference)o;
293
294       return myFile.equals(that.myFile);
295     }
296
297     public int hashCode() {
298       return myFile.hashCode();
299     }
300   }
301   private static class PsiDirectoryReference extends PsiFileReference {
302     private PsiDirectoryReference(@NotNull VirtualFile file, @NotNull Project project) {
303       super(file, project);
304       assert file.isDirectory() : file;
305     }
306
307     @Override
308     public PsiElement retrieve() {
309       return SelfElementInfo.restoreDirectoryFromVirtual(myFile, myProject);
310     }
311
312     @Override
313     public PsiFile getFile() {
314       return null;
315     }
316
317     @Override
318     public int getEndOffset() {
319       return -1;
320     }
321   }
322
323   public static PsiElement restoreFromStubIndex(PsiFileWithStubSupport fileImpl,
324                                                 int index,
325                                                 IStubElementType elementType) {
326     if (fileImpl == null) return null;
327     StubTree tree = fileImpl.getStubTree();
328
329     boolean foreign = tree == null;
330     if (foreign) {
331       if (fileImpl instanceof PsiFileImpl) {
332         // Note: as far as this is a realization of StubIndexReference fileImpl#getContentElementType() must be instance of IStubFileElementType
333         tree = ((PsiFileImpl)fileImpl).calcStubTree();
334       }
335       else {
336         return null;
337       }
338     }
339
340     List<StubElement<?>> list = tree.getPlainList();
341     if (index >= list.size()) return null;
342     StubElement stub = list.get(index);
343
344     if (stub.getStubType() != elementType) return null;
345
346     if (foreign) {
347       final PsiElement cachedPsi = ((StubBase)stub).getCachedPsi();
348       if (cachedPsi != null) return cachedPsi;
349
350       final ASTNode ast = fileImpl.findTreeForStub(tree, stub);
351       return ast != null ? ast.getPsi() : null;
352     }
353     else {
354       return stub.getPsi();
355     }
356   }
357
358   public static class StubIndexReference extends PsiAnchor {
359     private final VirtualFile myVirtualFile;
360     private final Project myProject;
361     private final int myIndex;
362     private final Language myLanguage;
363     private final IStubElementType myElementType;
364
365     public StubIndexReference(@NotNull final PsiFile file, final int index, @NotNull Language language, IStubElementType elementType) {
366       myLanguage = language;
367       myElementType = elementType;
368       myVirtualFile = file.getVirtualFile();
369       myProject = file.getProject();
370       myIndex = index;
371     }
372
373     @Override
374     @Nullable
375     public PsiFile getFile() {
376       if (myProject.isDisposed() || !myVirtualFile.isValid()) {
377         return null;
378       }
379       final PsiFile file = PsiManager.getInstance(myProject).findFile(myVirtualFile);
380       if (file == null) {
381         return null;
382       }
383       if (file.getLanguage() == myLanguage) {
384         return file;
385       }
386       return file.getViewProvider().getPsi(myLanguage);
387     }
388
389     @Override
390     public PsiElement retrieve() {
391       return ApplicationManager.getApplication().runReadAction(new NullableComputable<PsiElement>() {
392         @Override
393         public PsiElement compute() {
394           return restoreFromStubIndex((PsiFileWithStubSupport)getFile(), myIndex, myElementType);
395         }
396       });
397     }
398
399     @Override
400     public boolean equals(final Object o) {
401       if (this == o) return true;
402       if (!(o instanceof StubIndexReference)) return false;
403
404       final StubIndexReference that = (StubIndexReference)o;
405
406       return myIndex == that.myIndex &&
407              myVirtualFile.equals(that.myVirtualFile) &&
408              Comparing.equal(myElementType, that.myElementType) &&
409              myLanguage == that.myLanguage;
410     }
411
412     @Override
413     public int hashCode() {
414       return ((31 * myVirtualFile.hashCode() + myIndex) * 31 + (myElementType == null ? 0 : myElementType.hashCode())) * 31 + myLanguage.hashCode();
415     }
416
417     @NonNls
418     @Override
419     public String toString() {
420       return "StubIndexReference{" +
421              "myVirtualFile=" + myVirtualFile +
422              ", myProject=" + myProject +
423              ", myIndex=" + myIndex +
424              ", myLanguage=" + myLanguage +
425              ", myElementType=" + myElementType +
426              '}';
427     }
428
429     @Override
430     public int getStartOffset() {
431       final PsiElement resolved = retrieve();
432       if (resolved == null) throw new PsiInvalidElementAccessException(null);
433       return resolved.getTextRange().getStartOffset();
434     }
435
436     @Override
437     public int getEndOffset() {
438       final PsiElement resolved = retrieve();
439       if (resolved == null) throw new PsiInvalidElementAccessException(null);
440       return resolved.getTextRange().getEndOffset();
441     }
442
443     public VirtualFile getVirtualFile() {
444       return myVirtualFile;
445     }
446
447     public Project getProject() {
448       return myProject;
449     }
450   }
451 }
452