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