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.Comparing;
24 import com.intellij.openapi.util.NullableComputable;
25 import com.intellij.openapi.util.TextRange;
26 import com.intellij.openapi.vfs.VirtualFile;
27 import com.intellij.psi.impl.light.LightElement;
28 import com.intellij.psi.impl.smartPointers.SelfElementInfo;
29 import com.intellij.psi.impl.source.PsiFileImpl;
30 import com.intellij.psi.impl.source.PsiFileWithStubSupport;
31 import com.intellij.psi.stubs.IStubElementType;
32 import com.intellij.psi.stubs.StubBase;
33 import com.intellij.psi.stubs.StubElement;
34 import com.intellij.psi.stubs.StubTree;
35 import com.intellij.psi.tree.IStubFileElementType;
36 import com.intellij.psi.util.PsiTreeUtil;
37 import org.jetbrains.annotations.NonNls;
38 import org.jetbrains.annotations.NotNull;
39 import org.jetbrains.annotations.Nullable;
41 import java.util.List;
47 public abstract class PsiAnchor {
49 public abstract PsiElement retrieve();
50 public abstract PsiFile getFile();
51 public abstract int getStartOffset();
52 public abstract int getEndOffset();
54 public static PsiAnchor create(@NotNull final PsiElement element) {
55 if (!element.isValid()) {
56 throw new PsiInvalidElementAccessException(element);
59 if (element instanceof PsiFile) {
60 VirtualFile virtualFile = ((PsiFile)element).getVirtualFile();
61 if (virtualFile != null) return new PsiFileReference(virtualFile, element.getProject());
62 return new HardReference(element);
64 if (element instanceof PsiDirectory) {
65 VirtualFile virtualFile = ((PsiDirectory)element).getVirtualFile();
66 return new PsiDirectoryReference(virtualFile, element.getProject());
69 PsiFile file = element.getContainingFile();
71 return new HardReference(element);
73 VirtualFile virtualFile = file.getVirtualFile();
74 if (virtualFile == null) return new HardReference(element);
76 PsiAnchor stubRef = createStubReference(element, file);
77 if (stubRef != null) return stubRef;
79 if (!element.isPhysical() && element instanceof PsiCompiledElement|| element instanceof LightElement) {
80 return new HardReference(element);
83 TextRange textRange = element.getTextRange();
84 if (textRange == null) {
85 return new HardReference(element);
89 final FileViewProvider viewProvider = file.getViewProvider();
90 final Set<Language> languages = viewProvider.getLanguages();
91 for (Language l : languages) {
92 if (PsiTreeUtil.isAncestor(viewProvider.getPsi(l), element, false)) {
98 if (lang == null) lang = element.getLanguage();
99 return new TreeRangeReference(file, textRange.getStartOffset(), textRange.getEndOffset(), element.getClass(), lang, virtualFile);
103 public static StubIndexReference createStubReference(@NotNull PsiElement element, @NotNull PsiFile containingFile) {
104 if (element instanceof StubBasedPsiElement &&
105 element.isPhysical() &&
106 (element instanceof PsiCompiledElement || ((PsiFileImpl)containingFile).getContentElementType() instanceof IStubFileElementType)) {
107 final StubBasedPsiElement elt = (StubBasedPsiElement)element;
108 final IStubElementType elementType = elt.getElementType();
109 if (elt.getStub() != null || elementType.shouldCreateStub(element.getNode())) {
110 int index = calcStubIndex((StubBasedPsiElement)element);
112 return new StubIndexReference(containingFile, index, containingFile.getLanguage(), elementType);
119 public static int calcStubIndex(StubBasedPsiElement psi) {
120 if (psi instanceof PsiFile) {
124 final StubElement liveStub = psi.getStub();
125 if (liveStub != null) {
126 return ((StubBase)liveStub).id;
129 PsiFileImpl file = (PsiFileImpl)psi.getContainingFile();
130 final StubTree stubTree = file.calcStubTree();
131 for (StubElement<?> stb : stubTree.getPlainList()) {
132 if (stb.getPsi() == psi) {
133 return ((StubBase)stb).id;
137 return -1; // it is possible via custom stub builder intentionally not producing stubs for stubbed elements
140 private static class TreeRangeReference extends PsiAnchor {
141 private final VirtualFile myVirtualFile;
142 private final Project myProject;
143 private final Language myLanguage;
144 private final Language myFileLanguage;
145 private final int myStartOffset;
146 private final int myEndOffset;
147 private final Class myClass;
149 private TreeRangeReference(@NotNull PsiFile file,
152 @NotNull Class aClass,
153 @NotNull Language language,
154 @NotNull VirtualFile virtualFile) {
155 myVirtualFile = virtualFile;
156 myProject = file.getProject();
157 myStartOffset = startOffset;
158 myEndOffset = endOffset;
160 myLanguage = language;
161 myFileLanguage = file.getLanguage();
166 public PsiElement retrieve() {
167 PsiFile psiFile = getFile();
168 if (psiFile == null || !psiFile.isValid()) return null;
169 PsiElement element = psiFile.getViewProvider().findElementAt(myStartOffset, myLanguage);
170 if (element == null) return null;
172 while (!element.getClass().equals(myClass) ||
173 element.getTextRange().getStartOffset() != myStartOffset ||
174 element.getTextRange().getEndOffset() != myEndOffset) {
175 element = element.getParent();
176 if (element == null || element.getTextRange() == null) return null;
184 public PsiFile getFile() {
185 return SelfElementInfo.restoreFileFromVirtual(myVirtualFile, myProject, myLanguage);
189 public int getStartOffset() {
190 return myStartOffset;
194 public int getEndOffset() {
198 public boolean equals(Object o) {
199 if (this == o) return true;
200 if (!(o instanceof TreeRangeReference)) return false;
202 final TreeRangeReference that = (TreeRangeReference)o;
204 return myEndOffset == that.myEndOffset &&
205 myStartOffset == that.myStartOffset &&
206 myClass.equals(that.myClass) &&
207 myVirtualFile.equals(that.myVirtualFile);
210 public int hashCode() {
211 int result = myClass.getName().hashCode();
212 result = 31 * result + myStartOffset;
213 result = 31 * result + myEndOffset;
214 result = 31 * result + myVirtualFile.hashCode();
220 private static class HardReference extends PsiAnchor {
221 private final PsiElement myElement;
223 private HardReference(final PsiElement element) {
228 public PsiElement retrieve() {
233 public PsiFile getFile() {
234 return myElement.getContainingFile();
238 public int getStartOffset() {
239 return myElement.getTextRange().getStartOffset();
243 public int getEndOffset() {
244 return myElement.getTextRange().getEndOffset();
248 public boolean equals(final Object o) {
249 if (this == o) return true;
250 if (!(o instanceof HardReference)) return false;
252 final HardReference that = (HardReference)o;
254 return myElement.equals(that.myElement);
257 public int hashCode() {
258 return myElement.hashCode();
262 private static class PsiFileReference extends PsiAnchor {
263 protected final VirtualFile myFile;
264 protected final Project myProject;
266 private PsiFileReference(@NotNull VirtualFile file, @NotNull Project project) {
272 public PsiElement retrieve() {
277 public PsiFile getFile() {
278 return SelfElementInfo.restoreFileFromVirtual(myFile, myProject);
282 public int getStartOffset() {
287 public int getEndOffset() {
288 return (int)myFile.getLength();
291 public boolean equals(final Object o) {
292 if (this == o) return true;
293 if (!(o instanceof PsiFileReference)) return false;
295 final PsiFileReference that = (PsiFileReference)o;
297 return myFile.equals(that.myFile);
300 public int hashCode() {
301 return myFile.hashCode();
304 private static class PsiDirectoryReference extends PsiFileReference {
305 private PsiDirectoryReference(@NotNull VirtualFile file, @NotNull Project project) {
306 super(file, project);
307 assert file.isDirectory() : file;
311 public PsiElement retrieve() {
312 return SelfElementInfo.restoreDirectoryFromVirtual(myFile, myProject);
316 public PsiFile getFile() {
321 public int getEndOffset() {
326 public static PsiElement restoreFromStubIndex(PsiFileWithStubSupport fileImpl,
328 IStubElementType elementType) {
329 if (fileImpl == null) return null;
330 StubTree tree = fileImpl.getStubTree();
332 boolean foreign = tree == null;
334 if (fileImpl instanceof PsiFileImpl) {
335 // Note: as far as this is a realization of StubIndexReference fileImpl#getContentElementType() must be instance of IStubFileElementType
336 tree = ((PsiFileImpl)fileImpl).calcStubTree();
343 List<StubElement<?>> list = tree.getPlainList();
344 if (index >= list.size()) return null;
345 StubElement stub = list.get(index);
347 if (stub.getStubType() != elementType) return null;
350 final PsiElement cachedPsi = ((StubBase)stub).getCachedPsi();
351 if (cachedPsi != null) return cachedPsi;
353 final ASTNode ast = fileImpl.findTreeForStub(tree, stub);
354 return ast != null ? ast.getPsi() : null;
357 return stub.getPsi();
361 public static class StubIndexReference extends PsiAnchor {
362 private final VirtualFile myVirtualFile;
363 private final Project myProject;
364 private final int myIndex;
365 private final Language myLanguage;
366 private final IStubElementType myElementType;
368 public StubIndexReference(@NotNull final PsiFile file, final int index, @NotNull Language language, IStubElementType elementType) {
369 myLanguage = language;
370 myElementType = elementType;
371 myVirtualFile = file.getVirtualFile();
372 myProject = file.getProject();
378 public PsiFile getFile() {
379 if (myProject.isDisposed() || !myVirtualFile.isValid()) {
382 final PsiFile file = PsiManager.getInstance(myProject).findFile(myVirtualFile);
386 if (file.getLanguage() == myLanguage) {
389 return file.getViewProvider().getPsi(myLanguage);
393 public PsiElement retrieve() {
394 return ApplicationManager.getApplication().runReadAction(new NullableComputable<PsiElement>() {
396 public PsiElement compute() {
397 return restoreFromStubIndex((PsiFileWithStubSupport)getFile(), myIndex, myElementType);
403 public boolean equals(final Object o) {
404 if (this == o) return true;
405 if (!(o instanceof StubIndexReference)) return false;
407 final StubIndexReference that = (StubIndexReference)o;
409 return myIndex == that.myIndex &&
410 myVirtualFile.equals(that.myVirtualFile) &&
411 Comparing.equal(myElementType, that.myElementType) &&
412 myLanguage == that.myLanguage;
416 public int hashCode() {
417 return ((31 * myVirtualFile.hashCode() + myIndex) * 31 + (myElementType == null ? 0 : myElementType.hashCode())) * 31 + myLanguage.hashCode();
422 public String toString() {
423 return "StubIndexReference{" +
424 "myVirtualFile=" + myVirtualFile +
425 ", myProject=" + myProject +
426 ", myIndex=" + myIndex +
427 ", myLanguage=" + myLanguage +
428 ", myElementType=" + myElementType +
433 public int getStartOffset() {
434 final PsiElement resolved = retrieve();
435 if (resolved == null) throw new PsiInvalidElementAccessException(null);
436 return resolved.getTextRange().getStartOffset();
440 public int getEndOffset() {
441 final PsiElement resolved = retrieve();
442 if (resolved == null) throw new PsiInvalidElementAccessException(null);
443 return resolved.getTextRange().getEndOffset();
446 public VirtualFile getVirtualFile() {
447 return myVirtualFile;
450 public Project getProject() {