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