24121c111b39611e45104bb69dedb8420f46eaf2
[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.extapi.psi.StubPath;
20 import com.intellij.extapi.psi.StubPathBuilder;
21 import com.intellij.lang.ASTNode;
22 import com.intellij.lang.Language;
23 import com.intellij.openapi.application.ApplicationManager;
24 import com.intellij.openapi.util.Computable;
25 import com.intellij.openapi.util.NullableComputable;
26 import com.intellij.openapi.util.TextRange;
27 import com.intellij.psi.impl.light.LightElement;
28 import com.intellij.psi.impl.source.PsiFileImpl;
29 import com.intellij.psi.impl.source.PsiFileWithStubSupport;
30 import com.intellij.psi.stubs.StubBase;
31 import com.intellij.psi.stubs.StubElement;
32 import com.intellij.psi.stubs.StubTree;
33 import com.intellij.psi.tree.IStubFileElementType;
34 import com.intellij.psi.util.PsiTreeUtil;
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 PsiFile myFile;
215     private final int myIndex;
216
217     public StubIndexReference(final PsiFile file, final int index) {
218       myFile = file;
219       myIndex = index;
220     }
221
222     public PsiFile getFile() {
223       return myFile;
224     }
225
226     public PsiElement retrieve() {
227       return ApplicationManager.getApplication().runReadAction(new NullableComputable<PsiElement>() {
228         public PsiElement compute() {
229           PsiFileWithStubSupport fileImpl = (PsiFileWithStubSupport)myFile;
230           StubTree tree = fileImpl.getStubTree();
231
232           boolean foreign = (tree == null);
233           if (foreign) {
234             if (fileImpl instanceof PsiFileImpl) {
235               tree = ((PsiFileImpl)fileImpl).calcStubTree();
236             }
237             else {
238               return null;
239             }
240           }
241
242           StubElement stub = tree.getPlainList().get(myIndex);
243
244           if (foreign) {
245             final PsiElement cachedPsi = ((StubBase)stub).getCachedPsi();
246             if (cachedPsi != null) return cachedPsi;
247
248             final ASTNode ast = fileImpl.findTreeForStub(tree, stub);
249             return ast != null ? ast.getPsi() : null;
250           }
251           else {
252             return stub != null ? stub.getPsi() : null;
253           }
254         }
255       });
256     }
257
258     @Override
259     public boolean equals(final Object o) {
260       if (this == o) return true;
261       if (!(o instanceof StubIndexReference)) return false;
262
263       final StubIndexReference that = (StubIndexReference)o;
264
265       return myIndex == that.myIndex && myFile.equals(that.myFile);
266     }
267
268     @Override
269     public int hashCode() {
270       return 31 * myFile.hashCode() + myIndex;
271     }
272
273     public int getStartOffset() {
274       final PsiElement resolved = retrieve();
275       if (resolved == null) throw new PsiInvalidElementAccessException(null);
276       return resolved.getTextRange().getStartOffset();
277     }
278
279     public int getEndOffset() {
280       final PsiElement resolved = retrieve();
281       if (resolved == null) throw new PsiInvalidElementAccessException(null);
282       return resolved.getTextRange().getEndOffset();
283     }
284   }
285
286   private static class StubPathReference extends PsiAnchor {
287     private final PsiFile myFile;
288     private final StubPath myPath;
289
290     public StubPathReference(final PsiFile file, final StubPath path) {
291       myFile = file;
292       myPath = path;
293     }
294
295     public PsiElement retrieve() {
296       return ApplicationManager.getApplication().runReadAction(new Computable<PsiElement>() {
297         public PsiElement compute() {
298           return StubPathBuilder.resolve(myFile, myPath);
299         }
300       });
301     }
302
303     public PsiFile getFile() {
304       return myFile;
305     }
306
307     public int getStartOffset() {
308       final PsiElement resolved = retrieve();
309       if (resolved == null) throw new PsiInvalidElementAccessException(null);
310       return resolved.getTextRange().getStartOffset();
311     }
312
313     public int getEndOffset() {
314       final PsiElement resolved = retrieve();
315       if (resolved == null) throw new PsiInvalidElementAccessException(null);
316       return resolved.getTextRange().getEndOffset();
317     }
318
319     public boolean equals(final Object o) {
320       if (this == o) return true;
321
322       if (o instanceof StubPathReference) {
323         final StubPathReference that = (StubPathReference)o;
324         return myFile.equals(that.myFile) && myPath.equals(that.myPath);
325       }
326
327       return false;
328     }
329
330     public int hashCode() {
331       return 31 * myFile.hashCode() + myPath.hashCode();
332     }
333   }
334
335   
336 }
337