fix "IDEA-221944 Deadlock on opening second project" and support preloading for proje...
[idea/community.git] / platform / core-impl / src / com / intellij / psi / impl / smartPointers / SmartPointerManagerImpl.java
1 // Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
2 package com.intellij.psi.impl.smartPointers;
3
4 import com.intellij.openapi.application.ApplicationManager;
5 import com.intellij.openapi.diagnostic.Logger;
6 import com.intellij.openapi.editor.Document;
7 import com.intellij.openapi.editor.event.DocumentEvent;
8 import com.intellij.openapi.editor.impl.FrozenDocument;
9 import com.intellij.openapi.fileEditor.FileDocumentManager;
10 import com.intellij.openapi.project.Project;
11 import com.intellij.openapi.util.Key;
12 import com.intellij.openapi.util.ProperTextRange;
13 import com.intellij.openapi.util.TextRange;
14 import com.intellij.openapi.vfs.VirtualFile;
15 import com.intellij.psi.*;
16 import com.intellij.psi.impl.PsiDocumentManagerBase;
17 import com.intellij.psi.util.PsiUtilCore;
18 import com.intellij.reference.SoftReference;
19 import org.jetbrains.annotations.NotNull;
20 import org.jetbrains.annotations.Nullable;
21 import org.jetbrains.annotations.TestOnly;
22
23 import java.lang.ref.Reference;
24 import java.util.List;
25
26 public final class SmartPointerManagerImpl extends SmartPointerManager {
27   private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.smartPointers.SmartPointerManagerImpl");
28   private final Project myProject;
29   private final Key<SmartPointerTracker> POINTERS_KEY;
30   private final PsiDocumentManagerBase myPsiDocManager;
31
32   public SmartPointerManagerImpl(@NotNull Project project) {
33     myProject = project;
34     myPsiDocManager = (PsiDocumentManagerBase)PsiDocumentManager.getInstance(project);
35     POINTERS_KEY = Key.create("SMART_POINTERS " + (project.isDefault() ? "default" : project.hashCode()));
36   }
37
38   @NotNull
39   private static String anonymize(@NotNull Project project) {
40     return
41       (project.isDisposed() ? "(Disposed)" : "") +
42       (project.isDefault() ? "(Default)" : "") +
43       project.hashCode();
44   }
45
46   public void fastenBelts(@NotNull VirtualFile file) {
47     SmartPointerTracker pointers = getTracker(file);
48     if (pointers != null) pointers.fastenBelts(this);
49   }
50
51   private static final Key<Reference<SmartPsiElementPointerImpl<?>>> CACHED_SMART_POINTER_KEY = Key.create("CACHED_SMART_POINTER_KEY");
52   @Override
53   @NotNull
54   public <E extends PsiElement> SmartPsiElementPointer<E> createSmartPsiElementPointer(@NotNull E element) {
55     ApplicationManager.getApplication().assertReadAccessAllowed();
56     PsiFile containingFile = element.getContainingFile();
57     return createSmartPsiElementPointer(element, containingFile);
58   }
59   @Override
60   @NotNull
61   public <E extends PsiElement> SmartPsiElementPointer<E> createSmartPsiElementPointer(@NotNull E element, PsiFile containingFile) {
62     return createSmartPsiElementPointer(element, containingFile, false);
63   }
64
65   @NotNull
66   public <E extends PsiElement> SmartPsiElementPointer<E> createSmartPsiElementPointer(@NotNull E element,
67                                                                                        PsiFile containingFile,
68                                                                                        boolean forInjected) {
69     ensureValid(element, containingFile);
70     SmartPointerTracker.processQueue();
71     ensureMyProject(containingFile != null ? containingFile.getProject() : element.getProject());
72     SmartPsiElementPointerImpl<E> pointer = getCachedPointer(element);
73     if (pointer != null &&
74         (!(pointer.getElementInfo() instanceof SelfElementInfo) || ((SelfElementInfo)pointer.getElementInfo()).isForInjected() == forInjected) &&
75         pointer.incrementAndGetReferenceCount(1) > 0) {
76       return pointer;
77     }
78
79     pointer = new SmartPsiElementPointerImpl<>(this, element, containingFile, forInjected);
80     if (containingFile != null) {
81       trackPointer(pointer, containingFile.getViewProvider().getVirtualFile());
82     }
83     element.putUserData(CACHED_SMART_POINTER_KEY, new SoftReference<>(pointer));
84     return pointer;
85   }
86
87   private void ensureMyProject(@NotNull Project project) {
88     if (project != myProject) {
89       throw new IllegalArgumentException("Element from alien project: "+anonymize(project)+" expected: "+anonymize(myProject));
90     }
91   }
92
93   private static void ensureValid(@NotNull PsiElement element, @Nullable PsiFile containingFile) {
94     boolean valid = containingFile != null ? containingFile.isValid() : element.isValid();
95     if (!valid) {
96       PsiUtilCore.ensureValid(element);
97       if (containingFile != null && !containingFile.isValid()) {
98         throw new PsiInvalidElementAccessException(containingFile, "Element " + element.getClass() + "(" + element.getLanguage() + ")" + " claims to be valid but returns invalid containing file ");
99       }
100     }
101   }
102
103   private static <E extends PsiElement> SmartPsiElementPointerImpl<E> getCachedPointer(@NotNull E element) {
104     Reference<SmartPsiElementPointerImpl<?>> data = element.getUserData(CACHED_SMART_POINTER_KEY);
105     SmartPsiElementPointerImpl<?> cachedPointer = SoftReference.dereference(data);
106     if (cachedPointer != null) {
107       PsiElement cachedElement = cachedPointer.getElement();
108       if (cachedElement != element) {
109         return null;
110       }
111     }
112     //noinspection unchecked
113     return (SmartPsiElementPointerImpl<E>)cachedPointer;
114   }
115
116   @Override
117   @NotNull
118   public SmartPsiFileRange createSmartPsiFileRangePointer(@NotNull PsiFile file, @NotNull TextRange range) {
119     return createSmartPsiFileRangePointer(file, range, false);
120   }
121
122   @NotNull
123   public SmartPsiFileRange createSmartPsiFileRangePointer(@NotNull PsiFile file,
124                                                           @NotNull TextRange range,
125                                                           boolean forInjected) {
126     PsiUtilCore.ensureValid(file);
127     SmartPointerTracker.processQueue();
128     SmartPsiFileRangePointerImpl pointer = new SmartPsiFileRangePointerImpl(this, file, ProperTextRange.create(range), forInjected);
129     trackPointer(pointer, file.getViewProvider().getVirtualFile());
130
131     return pointer;
132   }
133
134   private <E extends PsiElement> void trackPointer(@NotNull SmartPsiElementPointerImpl<E> pointer, @NotNull VirtualFile containingFile) {
135     SmartPointerElementInfo info = pointer.getElementInfo();
136     if (!(info instanceof SelfElementInfo)) return;
137
138     SmartPointerTracker.PointerReference reference = new SmartPointerTracker.PointerReference(pointer, containingFile, POINTERS_KEY);
139     while (true) {
140       SmartPointerTracker pointers = getTracker(containingFile);
141       if (pointers == null) {
142         pointers = containingFile.putUserDataIfAbsent(POINTERS_KEY, new SmartPointerTracker());
143       }
144       if (pointers.addReference(reference, pointer)) {
145         break;
146       }
147     }
148   }
149
150   @Override
151   public void removePointer(@NotNull SmartPsiElementPointer pointer) {
152     if (!(pointer instanceof SmartPsiElementPointerImpl) || myProject.isDisposed()) {
153       return;
154     }
155     ensureMyProject(pointer.getProject());
156     int refCount = ((SmartPsiElementPointerImpl<?>)pointer).incrementAndGetReferenceCount(-1);
157     if (refCount == -1) {
158       LOG.error("Double smart pointer removal");
159       return;
160     }
161
162     if (refCount == 0) {
163       PsiElement element = ((SmartPointerEx<?>)pointer).getCachedElement();
164       if (element != null) {
165         element.putUserData(CACHED_SMART_POINTER_KEY, null);
166       }
167
168       SmartPointerElementInfo info = ((SmartPsiElementPointerImpl<?>)pointer).getElementInfo();
169       info.cleanup();
170
171       SmartPointerTracker.PointerReference reference = ((SmartPsiElementPointerImpl<?>)pointer).pointerReference;
172       if (reference != null) {
173         if (reference.get() != pointer) {
174           throw new IllegalStateException("Reference points to " + reference.get());
175         }
176         if (reference.key != POINTERS_KEY) {
177           throw new IllegalStateException("Reference from wrong project: " + reference.key + " vs " + POINTERS_KEY);
178         }
179         SmartPointerTracker pointers = getTracker(reference.file);
180         if (pointers != null) {
181           pointers.removeReference(reference);
182         }
183       }
184     }
185   }
186
187   @Nullable
188   SmartPointerTracker getTracker(@NotNull VirtualFile containingFile) {
189     return containingFile.getUserData(POINTERS_KEY);
190   }
191
192   @TestOnly
193   public int getPointersNumber(@NotNull PsiFile containingFile) {
194     VirtualFile file = containingFile.getViewProvider().getVirtualFile();
195     SmartPointerTracker pointers = getTracker(file);
196     return pointers == null ? 0 : pointers.getSize();
197   }
198
199   @Override
200   public boolean pointToTheSameElement(@NotNull SmartPsiElementPointer pointer1, @NotNull SmartPsiElementPointer pointer2) {
201     return SmartPsiElementPointerImpl.pointsToTheSameElementAs(pointer1, pointer2);
202   }
203
204   public void updatePointers(@NotNull Document document, @NotNull FrozenDocument frozen, @NotNull List<? extends DocumentEvent> events) {
205     VirtualFile file = FileDocumentManager.getInstance().getFile(document);
206     SmartPointerTracker list = file == null ? null : getTracker(file);
207     if (list != null) list.updateMarkers(frozen, events);
208   }
209
210   public void updatePointerTargetsAfterReparse(@NotNull VirtualFile file) {
211     SmartPointerTracker list = getTracker(file);
212     if (list != null) list.updatePointerTargetsAfterReparse();
213   }
214
215   @NotNull
216   Project getProject() {
217     return myProject;
218   }
219
220   @NotNull
221   PsiDocumentManagerBase getPsiDocumentManager() {
222     return myPsiDocManager;
223   }
224 }