2 * Copyright 2000-2009 JetBrains s.r.o.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.intellij.psi;
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;
42 public abstract class PsiAnchor {
44 public abstract PsiElement retrieve();
45 public abstract PsiFile getFile();
46 public abstract int getStartOffset();
47 public abstract int getEndOffset();
49 public static PsiAnchor create(final PsiElement element) {
50 if (element instanceof PsiFile) {
51 return new HardReference(element);
54 PsiFile file = element.getContainingFile();
56 return new HardReference(element);
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));
66 TextRange textRange = element.getTextRange();
67 if (textRange == null || element instanceof LightElement) {
68 return new HardReference(element);
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)) {
81 if (lang == null) lang = element.getLanguage();
82 return new TreeRangeReference(file, textRange.getStartOffset(), textRange.getEndOffset(), element.getClass(), lang);
85 public static int calcStubIndex(StubBasedPsiElement psi) {
86 if (psi instanceof PsiFile) {
90 final StubElement liveStub = psi.getStub();
91 if (liveStub != null) {
92 return ((StubBase)liveStub).id;
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;
103 throw new RuntimeException("Can't find stub index for this psi");
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;
114 private TreeRangeReference(final PsiFile file, final int startOffset, final int endOffset, final Class aClass, final Language language) {
116 myStartOffset = startOffset;
117 myEndOffset = endOffset;
119 myLanguage = language;
123 public PsiElement retrieve() {
124 PsiElement element = myFile.getViewProvider().findElementAt(myStartOffset, myLanguage);
125 if (element == null) return null;
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;
137 public PsiFile getFile() {
141 public int getStartOffset() {
142 return myStartOffset;
145 public int getEndOffset() {
149 public boolean equals(Object o) {
150 if (this == o) return true;
151 if (!(o instanceof TreeRangeReference)) return false;
153 final TreeRangeReference that = (TreeRangeReference)o;
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;
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();
175 private static class HardReference extends PsiAnchor {
176 private final PsiElement myElement;
178 private HardReference(final PsiElement element) {
182 public PsiElement retrieve() {
186 public PsiFile getFile() {
187 return myElement.getContainingFile();
190 public int getStartOffset() {
191 return myElement.getTextRange().getStartOffset();
194 public int getEndOffset() {
195 return myElement.getTextRange().getEndOffset();
199 public boolean equals(final Object o) {
200 if (this == o) return true;
201 if (!(o instanceof HardReference)) return false;
203 final HardReference that = (HardReference)o;
205 return myElement.equals(that.myElement);
208 public int hashCode() {
209 return myElement.hashCode();
213 public static class StubIndexReference extends PsiAnchor {
214 private final PsiFile myFile;
215 private final int myIndex;
217 public StubIndexReference(final PsiFile file, final int index) {
222 public PsiFile getFile() {
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();
232 boolean foreign = (tree == null);
234 if (fileImpl instanceof PsiFileImpl) {
235 tree = ((PsiFileImpl)fileImpl).calcStubTree();
242 StubElement stub = tree.getPlainList().get(myIndex);
245 final PsiElement cachedPsi = ((StubBase)stub).getCachedPsi();
246 if (cachedPsi != null) return cachedPsi;
248 final ASTNode ast = fileImpl.findTreeForStub(tree, stub);
249 return ast != null ? ast.getPsi() : null;
252 return stub != null ? stub.getPsi() : null;
259 public boolean equals(final Object o) {
260 if (this == o) return true;
261 if (!(o instanceof StubIndexReference)) return false;
263 final StubIndexReference that = (StubIndexReference)o;
265 return myIndex == that.myIndex && myFile.equals(that.myFile);
269 public int hashCode() {
270 return 31 * myFile.hashCode() + myIndex;
273 public int getStartOffset() {
274 final PsiElement resolved = retrieve();
275 if (resolved == null) throw new PsiInvalidElementAccessException(null);
276 return resolved.getTextRange().getStartOffset();
279 public int getEndOffset() {
280 final PsiElement resolved = retrieve();
281 if (resolved == null) throw new PsiInvalidElementAccessException(null);
282 return resolved.getTextRange().getEndOffset();
286 private static class StubPathReference extends PsiAnchor {
287 private final PsiFile myFile;
288 private final StubPath myPath;
290 public StubPathReference(final PsiFile file, final StubPath path) {
295 public PsiElement retrieve() {
296 return ApplicationManager.getApplication().runReadAction(new Computable<PsiElement>() {
297 public PsiElement compute() {
298 return StubPathBuilder.resolve(myFile, myPath);
303 public PsiFile getFile() {
307 public int getStartOffset() {
308 final PsiElement resolved = retrieve();
309 if (resolved == null) throw new PsiInvalidElementAccessException(null);
310 return resolved.getTextRange().getStartOffset();
313 public int getEndOffset() {
314 final PsiElement resolved = retrieve();
315 if (resolved == null) throw new PsiInvalidElementAccessException(null);
316 return resolved.getTextRange().getEndOffset();
319 public boolean equals(final Object o) {
320 if (this == o) return true;
322 if (o instanceof StubPathReference) {
323 final StubPathReference that = (StubPathReference)o;
324 return myFile.equals(that.myFile) && myPath.equals(that.myPath);
330 public int hashCode() {
331 return 31 * myFile.hashCode() + myPath.hashCode();