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.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;
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 VirtualFile myVirtualFile;
215 private final Project myProject;
216 private final int myIndex;
218 public StubIndexReference(@NotNull PsiFile file, final int index) {
219 myVirtualFile = file.getVirtualFile();
220 myProject = file.getProject();
224 public PsiFile getFile() {
225 if (myProject.isDisposed()) return null;
226 return PsiManager.getInstance(myProject).findFile(myVirtualFile);
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();
236 boolean foreign = tree == null;
238 if (fileImpl instanceof PsiFileImpl) {
239 tree = ((PsiFileImpl)fileImpl).calcStubTree();
246 StubElement stub = tree.getPlainList().get(myIndex);
249 final PsiElement cachedPsi = ((StubBase)stub).getCachedPsi();
250 if (cachedPsi != null) return cachedPsi;
252 final ASTNode ast = fileImpl.findTreeForStub(tree, stub);
253 return ast != null ? ast.getPsi() : null;
256 return stub != null ? stub.getPsi() : null;
263 public boolean equals(final Object o) {
264 if (this == o) return true;
265 if (!(o instanceof StubIndexReference)) return false;
267 final StubIndexReference that = (StubIndexReference)o;
269 return myIndex == that.myIndex && myVirtualFile.equals(that.myVirtualFile);
273 public int hashCode() {
274 return 31 * myVirtualFile.hashCode() + myIndex;
277 public int getStartOffset() {
278 final PsiElement resolved = retrieve();
279 if (resolved == null) throw new PsiInvalidElementAccessException(null);
280 return resolved.getTextRange().getStartOffset();
283 public int getEndOffset() {
284 final PsiElement resolved = retrieve();
285 if (resolved == null) throw new PsiInvalidElementAccessException(null);
286 return resolved.getTextRange().getEndOffset();