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 int myStartOffset;
145 private final int myEndOffset;
146 private final Class myClass;
148 private TreeRangeReference(@NotNull PsiFile file,
151 @NotNull Class aClass,
152 @NotNull Language language,
153 @NotNull VirtualFile virtualFile) {
154 myVirtualFile = virtualFile;
155 myProject = file.getProject();
156 myStartOffset = startOffset;
157 myEndOffset = endOffset;
159 myLanguage = language;
164 public PsiElement retrieve() {
165 PsiFile psiFile = getFile();
166 if (psiFile == null || !psiFile.isValid()) return null;
167 PsiElement element = psiFile.getViewProvider().findElementAt(myStartOffset, myLanguage);
168 if (element == null) return null;
170 while (!element.getClass().equals(myClass) ||
171 element.getTextRange().getStartOffset() != myStartOffset ||
172 element.getTextRange().getEndOffset() != myEndOffset) {
173 element = element.getParent();
174 if (element == null || element.getTextRange() == null) return null;
181 public PsiFile getFile() {
182 return SelfElementInfo.restoreFileFromVirtual(myVirtualFile, myProject);
186 public int getStartOffset() {
187 return myStartOffset;
191 public int getEndOffset() {
195 public boolean equals(Object o) {
196 if (this == o) return true;
197 if (!(o instanceof TreeRangeReference)) return false;
199 final TreeRangeReference that = (TreeRangeReference)o;
201 return myEndOffset == that.myEndOffset &&
202 myStartOffset == that.myStartOffset &&
203 myClass.equals(that.myClass) &&
204 myVirtualFile.equals(that.myVirtualFile);
207 public int hashCode() {
208 int result = myClass.getName().hashCode();
209 result = 31 * result + myStartOffset;
210 result = 31 * result + myEndOffset;
211 result = 31 * result + myVirtualFile.hashCode();
217 private static class HardReference extends PsiAnchor {
218 private final PsiElement myElement;
220 private HardReference(final PsiElement element) {
225 public PsiElement retrieve() {
230 public PsiFile getFile() {
231 return myElement.getContainingFile();
235 public int getStartOffset() {
236 return myElement.getTextRange().getStartOffset();
240 public int getEndOffset() {
241 return myElement.getTextRange().getEndOffset();
245 public boolean equals(final Object o) {
246 if (this == o) return true;
247 if (!(o instanceof HardReference)) return false;
249 final HardReference that = (HardReference)o;
251 return myElement.equals(that.myElement);
254 public int hashCode() {
255 return myElement.hashCode();
259 private static class PsiFileReference extends PsiAnchor {
260 protected final VirtualFile myFile;
261 protected final Project myProject;
263 private PsiFileReference(@NotNull VirtualFile file, @NotNull Project project) {
269 public PsiElement retrieve() {
274 public PsiFile getFile() {
275 return SelfElementInfo.restoreFileFromVirtual(myFile, myProject);
279 public int getStartOffset() {
284 public int getEndOffset() {
285 return (int)myFile.getLength();
288 public boolean equals(final Object o) {
289 if (this == o) return true;
290 if (!(o instanceof PsiFileReference)) return false;
292 final PsiFileReference that = (PsiFileReference)o;
294 return myFile.equals(that.myFile);
297 public int hashCode() {
298 return myFile.hashCode();
301 private static class PsiDirectoryReference extends PsiFileReference {
302 private PsiDirectoryReference(@NotNull VirtualFile file, @NotNull Project project) {
303 super(file, project);
304 assert file.isDirectory() : file;
308 public PsiElement retrieve() {
309 return SelfElementInfo.restoreDirectoryFromVirtual(myFile, myProject);
313 public PsiFile getFile() {
318 public int getEndOffset() {
323 public static PsiElement restoreFromStubIndex(PsiFileWithStubSupport fileImpl,
325 IStubElementType elementType) {
326 if (fileImpl == null) return null;
327 StubTree tree = fileImpl.getStubTree();
329 boolean foreign = tree == null;
331 if (fileImpl instanceof PsiFileImpl) {
332 // Note: as far as this is a realization of StubIndexReference fileImpl#getContentElementType() must be instance of IStubFileElementType
333 tree = ((PsiFileImpl)fileImpl).calcStubTree();
340 List<StubElement<?>> list = tree.getPlainList();
341 if (index >= list.size()) return null;
342 StubElement stub = list.get(index);
344 if (stub.getStubType() != elementType) return null;
347 final PsiElement cachedPsi = ((StubBase)stub).getCachedPsi();
348 if (cachedPsi != null) return cachedPsi;
350 final ASTNode ast = fileImpl.findTreeForStub(tree, stub);
351 return ast != null ? ast.getPsi() : null;
354 return stub.getPsi();
358 public static class StubIndexReference extends PsiAnchor {
359 private final VirtualFile myVirtualFile;
360 private final Project myProject;
361 private final int myIndex;
362 private final Language myLanguage;
363 private final IStubElementType myElementType;
365 public StubIndexReference(@NotNull final PsiFile file, final int index, @NotNull Language language, IStubElementType elementType) {
366 myLanguage = language;
367 myElementType = elementType;
368 myVirtualFile = file.getVirtualFile();
369 myProject = file.getProject();
375 public PsiFile getFile() {
376 if (myProject.isDisposed() || !myVirtualFile.isValid()) {
379 final PsiFile file = PsiManager.getInstance(myProject).findFile(myVirtualFile);
383 if (file.getLanguage() == myLanguage) {
386 return file.getViewProvider().getPsi(myLanguage);
390 public PsiElement retrieve() {
391 return ApplicationManager.getApplication().runReadAction(new NullableComputable<PsiElement>() {
393 public PsiElement compute() {
394 return restoreFromStubIndex((PsiFileWithStubSupport)getFile(), myIndex, myElementType);
400 public boolean equals(final Object o) {
401 if (this == o) return true;
402 if (!(o instanceof StubIndexReference)) return false;
404 final StubIndexReference that = (StubIndexReference)o;
406 return myIndex == that.myIndex &&
407 myVirtualFile.equals(that.myVirtualFile) &&
408 Comparing.equal(myElementType, that.myElementType) &&
409 myLanguage == that.myLanguage;
413 public int hashCode() {
414 return ((31 * myVirtualFile.hashCode() + myIndex) * 31 + (myElementType == null ? 0 : myElementType.hashCode())) * 31 + myLanguage.hashCode();
419 public String toString() {
420 return "StubIndexReference{" +
421 "myVirtualFile=" + myVirtualFile +
422 ", myProject=" + myProject +
423 ", myIndex=" + myIndex +
424 ", myLanguage=" + myLanguage +
425 ", myElementType=" + myElementType +
430 public int getStartOffset() {
431 final PsiElement resolved = retrieve();
432 if (resolved == null) throw new PsiInvalidElementAccessException(null);
433 return resolved.getTextRange().getStartOffset();
437 public int getEndOffset() {
438 final PsiElement resolved = retrieve();
439 if (resolved == null) throw new PsiInvalidElementAccessException(null);
440 return resolved.getTextRange().getEndOffset();
443 public VirtualFile getVirtualFile() {
444 return myVirtualFile;
447 public Project getProject() {