3acb9d2e850686f46a42badbadde58deaae20f58
[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.NullableComputable;
24 import com.intellij.openapi.util.TextRange;
25 import com.intellij.openapi.vfs.VirtualFile;
26 import com.intellij.psi.impl.light.LightElement;
27 import com.intellij.psi.impl.source.PsiFileImpl;
28 import com.intellij.psi.impl.source.PsiFileWithStubSupport;
29 import com.intellij.psi.stubs.StubBase;
30 import com.intellij.psi.stubs.StubElement;
31 import com.intellij.psi.stubs.StubTree;
32 import com.intellij.psi.tree.IStubFileElementType;
33 import com.intellij.psi.util.PsiTreeUtil;
34 import org.jetbrains.annotations.NotNull;
35 import org.jetbrains.annotations.Nullable;
36
37 import java.util.Set;
38
39 /**
40  * @author db
41  */
42 public abstract class PsiAnchor {
43   @Nullable
44   public abstract PsiElement retrieve();
45   public abstract PsiFile getFile();
46   public abstract int getStartOffset();
47   public abstract int getEndOffset();
48
49   public static PsiAnchor create(final PsiElement element) {
50     if (element instanceof PsiFile) {
51       return new HardReference(element);
52     }
53
54     PsiFile file = element.getContainingFile();
55     if (file == null) {
56       return new HardReference(element);
57     }
58
59     if (element instanceof StubBasedPsiElement && element.isPhysical() && (element instanceof PsiCompiledElement || ((PsiFileImpl)file).getContentElementType() instanceof IStubFileElementType)) {
60       final StubBasedPsiElement elt = (StubBasedPsiElement)element;
61       if (elt.getStub() != null || elt.getElementType().shouldCreateStub(element.getNode())) {
62         return new StubIndexReference(file, calcStubIndex((StubBasedPsiElement)element));
63       }
64     }
65
66     TextRange textRange = element.getTextRange();
67     if (textRange == null || element instanceof LightElement) {
68       return new HardReference(element);
69     }
70
71     Language lang = null;
72     final FileViewProvider viewProvider = file.getViewProvider();
73     final Set<Language> languages = viewProvider.getLanguages();
74     for (Language l : languages) {
75       if (PsiTreeUtil.isAncestor(viewProvider.getPsi(l), element, false)) {
76         lang = l;
77         break;
78       }
79     }
80
81     if (lang == null) lang = element.getLanguage();
82     return new TreeRangeReference(file, textRange.getStartOffset(), textRange.getEndOffset(), element.getClass(), lang);
83   }
84
85   public static int calcStubIndex(StubBasedPsiElement psi) {
86     if (psi instanceof PsiFile) {
87       return 0;
88     }
89
90     final StubElement liveStub = psi.getStub();
91     if (liveStub != null) {
92       return ((StubBase)liveStub).id;
93     }
94
95     PsiFileImpl file = (PsiFileImpl)psi.getContainingFile();
96     final StubTree stubTree = file.calcStubTree();
97     for (StubElement<?> stb : stubTree.getPlainList()) {
98       if (stb.getPsi() == psi) {
99         return ((StubBase)stb).id;
100       }
101     }
102
103     throw new RuntimeException("Can't find stub index for this psi");
104   }
105
106
107   private static class TreeRangeReference extends PsiAnchor {
108     private final PsiFile myFile;
109     private final Language myLanguage;
110     private final int myStartOffset;
111     private final int myEndOffset;
112     private final Class myClass;
113
114     private TreeRangeReference(final PsiFile file, final int startOffset, final int endOffset, final Class aClass, final Language language) {
115       myFile = file;
116       myStartOffset = startOffset;
117       myEndOffset = endOffset;
118       myClass = aClass;
119       myLanguage = language;
120     }
121
122     @Nullable
123     public PsiElement retrieve() {
124       PsiElement element = myFile.getViewProvider().findElementAt(myStartOffset, myLanguage);
125       if (element == null) return null;
126
127       while  (!element.getClass().equals(myClass) ||
128               element.getTextRange().getStartOffset() != myStartOffset ||
129               element.getTextRange().getEndOffset() != myEndOffset) {
130         element = element.getParent();
131         if (element == null || element.getTextRange() == null) return null;
132       }
133
134       return element;
135     }
136
137     public PsiFile getFile() {
138       return myFile;
139     }
140
141     public int getStartOffset() {
142       return myStartOffset;
143     }
144
145     public int getEndOffset() {
146       return myEndOffset;
147     }
148
149     public boolean equals(Object o) {
150       if (this == o) return true;
151       if (!(o instanceof TreeRangeReference)) return false;
152
153       final TreeRangeReference that = (TreeRangeReference)o;
154
155       if (myEndOffset != that.myEndOffset) return false;
156       if (myStartOffset != that.myStartOffset) return false;
157       if (myClass != null ? !myClass.equals(that.myClass) : that.myClass != null) return false;
158       if (myFile != null ? !myFile.equals(that.myFile) : that.myFile != null) return false;
159
160       return true;
161     }
162
163     public int hashCode() {
164       int result = myClass != null ? myClass.getName().hashCode() : 0;
165       result = 31 * result + myStartOffset; //todo
166       result = 31 * result + myEndOffset;
167       if (myFile != null) {
168         result = 31 * result + myFile.getName().hashCode();
169       }
170
171       return result;
172     }
173   }
174
175   private static class HardReference extends PsiAnchor {
176     private final PsiElement myElement;
177
178     private HardReference(final PsiElement element) {
179       myElement = element;
180     }
181
182     public PsiElement retrieve() {
183       return myElement;
184     }
185
186     public PsiFile getFile() {
187       return myElement.getContainingFile();
188     }
189
190     public int getStartOffset() {
191       return myElement.getTextRange().getStartOffset();
192     }
193
194     public int getEndOffset() {
195       return myElement.getTextRange().getEndOffset();
196     }
197
198
199     public boolean equals(final Object o) {
200       if (this == o) return true;
201       if (!(o instanceof HardReference)) return false;
202
203       final HardReference that = (HardReference)o;
204
205       return myElement.equals(that.myElement);
206     }
207
208     public int hashCode() {
209       return myElement.hashCode();
210     }
211   }
212
213   public static class StubIndexReference extends PsiAnchor {
214     private final VirtualFile myVirtualFile;
215     private final Project myProject;
216     private final int myIndex;
217
218     public StubIndexReference(@NotNull PsiFile file, final int index) {
219       myVirtualFile = file.getVirtualFile();
220       myProject = file.getProject();
221       myIndex = index;
222     }
223
224     public PsiFile getFile() {
225       if (myProject.isDisposed()) return null;
226       return PsiManager.getInstance(myProject).findFile(myVirtualFile);
227     }
228
229     public PsiElement retrieve() {
230       return ApplicationManager.getApplication().runReadAction(new NullableComputable<PsiElement>() {
231         public PsiElement compute() {
232           PsiFileWithStubSupport fileImpl = (PsiFileWithStubSupport)getFile();
233           if (fileImpl == null) return null;
234           StubTree tree = fileImpl.getStubTree();
235
236           boolean foreign = tree == null;
237           if (foreign) {
238             if (fileImpl instanceof PsiFileImpl) {
239               tree = ((PsiFileImpl)fileImpl).calcStubTree();
240             }
241             else {
242               return null;
243             }
244           }
245
246           StubElement stub = tree.getPlainList().get(myIndex);
247
248           if (foreign) {
249             final PsiElement cachedPsi = ((StubBase)stub).getCachedPsi();
250             if (cachedPsi != null) return cachedPsi;
251
252             final ASTNode ast = fileImpl.findTreeForStub(tree, stub);
253             return ast != null ? ast.getPsi() : null;
254           }
255           else {
256             return stub != null ? stub.getPsi() : null;
257           }
258         }
259       });
260     }
261
262     @Override
263     public boolean equals(final Object o) {
264       if (this == o) return true;
265       if (!(o instanceof StubIndexReference)) return false;
266
267       final StubIndexReference that = (StubIndexReference)o;
268
269       return myIndex == that.myIndex && myVirtualFile.equals(that.myVirtualFile);
270     }
271
272     @Override
273     public int hashCode() {
274       return 31 * myVirtualFile.hashCode() + myIndex;
275     }
276
277     public int getStartOffset() {
278       final PsiElement resolved = retrieve();
279       if (resolved == null) throw new PsiInvalidElementAccessException(null);
280       return resolved.getTextRange().getStartOffset();
281     }
282
283     public int getEndOffset() {
284       final PsiElement resolved = retrieve();
285       if (resolved == null) throw new PsiInvalidElementAccessException(null);
286       return resolved.getTextRange().getEndOffset();
287     }
288   }
289 }
290