fix "IDEA-221944 Deadlock on opening second project" and support preloading for proje...
[idea/community.git] / platform / projectModel-impl / src / com / intellij / openapi / roots / impl / ModuleRootManagerImpl.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.openapi.roots.impl;
3
4 import com.intellij.openapi.Disposable;
5 import com.intellij.openapi.application.ApplicationManager;
6 import com.intellij.openapi.application.ReadAction;
7 import com.intellij.openapi.application.WriteAction;
8 import com.intellij.openapi.diagnostic.Logger;
9 import com.intellij.openapi.module.ModifiableModuleModel;
10 import com.intellij.openapi.module.Module;
11 import com.intellij.openapi.module.ModuleManager;
12 import com.intellij.openapi.module.ModuleServiceManager;
13 import com.intellij.openapi.project.Project;
14 import com.intellij.openapi.projectRoots.Sdk;
15 import com.intellij.openapi.roots.*;
16 import com.intellij.openapi.roots.ex.ProjectRootManagerEx;
17 import com.intellij.openapi.util.Disposer;
18 import com.intellij.openapi.util.InvalidDataException;
19 import com.intellij.openapi.util.JDOMExternalizable;
20 import com.intellij.openapi.util.SimpleModificationTracker;
21 import com.intellij.openapi.util.registry.Registry;
22 import com.intellij.openapi.vfs.VirtualFile;
23 import com.intellij.openapi.vfs.pointers.VirtualFilePointerManager;
24 import com.intellij.util.ThrowableRunnable;
25 import gnu.trove.THashMap;
26 import kotlin.NotImplementedError;
27 import org.jdom.Element;
28 import org.jetbrains.annotations.NotNull;
29 import org.jetbrains.annotations.Nullable;
30 import org.jetbrains.annotations.TestOnly;
31 import org.jetbrains.jps.model.module.JpsModuleSourceRootType;
32
33 import java.util.*;
34
35 public class ModuleRootManagerImpl extends ModuleRootManagerEx implements Disposable {
36   protected static final Logger LOG = Logger.getInstance("#com.intellij.openapi.roots.impl.ModuleRootManagerImpl");
37
38   private final Module myModule;
39   private final ProjectRootManagerImpl myProjectRootManager;
40   private final VirtualFilePointerManager myFilePointerManager;
41   protected RootModelImpl myRootModel;
42   private boolean myIsDisposed;
43   private boolean myLoaded;
44   private final OrderRootsCache myOrderRootsCache;
45   private final Map<RootModelImpl, Throwable> myModelCreations = new THashMap<>();
46
47   protected final SimpleModificationTracker myModificationTracker = new SimpleModificationTracker();
48
49   public ModuleRootManagerImpl(@NotNull Module module) {
50     myModule = module;
51     myProjectRootManager = ProjectRootManagerImpl.getInstanceImpl(module.getProject());
52     myFilePointerManager = VirtualFilePointerManager.getInstance();
53
54     myRootModel = new RootModelImpl(this, myProjectRootManager, myFilePointerManager);
55     myOrderRootsCache = new OrderRootsCache(module);
56   }
57
58   @Override
59   @NotNull
60   public Module getModule() {
61     return myModule;
62   }
63
64   @Override
65   @NotNull
66   public ModuleFileIndex getFileIndex() {
67     return ModuleServiceManager.getService(myModule, ModuleFileIndex.class);
68   }
69
70   @Override
71   public void dispose() {
72     myRootModel.dispose();
73     myIsDisposed = true;
74
75     if (Disposer.isDebugMode()) {
76       List<Map.Entry<RootModelImpl, Throwable>> entries;
77       synchronized (myModelCreations) {
78         entries = new ArrayList<>(myModelCreations.entrySet());
79       }
80       for (final Map.Entry<RootModelImpl, Throwable> entry : entries) {
81         LOG.warn("\n" +
82                  "***********************************************************************************************\n" +
83                  "***                        R O O T   M O D E L   N O T   D I S P O S E D                    ***\n" +
84                  "***********************************************************************************************\n" +
85                  "Created at:", entry.getValue());
86         entry.getKey().dispose();
87       }
88     }
89   }
90
91   @Override
92   @NotNull
93   public ModifiableRootModel getModifiableModel() {
94     return getModifiableModel(new RootConfigurationAccessor());
95   }
96
97   @Override
98   @NotNull
99   public ModifiableRootModel getModifiableModel(@NotNull RootConfigurationAccessor accessor) {
100     ApplicationManager.getApplication().assertReadAccessAllowed();
101     final RootModelImpl model = new RootModelImpl(myRootModel, this, true, accessor, myFilePointerManager, myProjectRootManager) {
102       @Override
103       public void dispose() {
104         super.dispose();
105         if (Disposer.isDebugMode()) {
106           synchronized (myModelCreations) {
107             myModelCreations.remove(this);
108           }
109         }
110       }
111     };
112     if (Disposer.isDebugMode()) {
113       synchronized (myModelCreations) {
114         myModelCreations.put(model, new Throwable());
115       }
116     }
117     return model;
118   }
119
120   @Override
121   @TestOnly
122   public long getModificationCountForTests() {
123     throw new NotImplementedError("Make sense only for persistent root manager");
124   }
125
126   void makeRootsChange(@NotNull Runnable runnable) {
127     ProjectRootManagerEx projectRootManagerEx = (ProjectRootManagerEx)ProjectRootManager.getInstance(myModule.getProject());
128     // IMPORTANT: should be the first listener!
129     projectRootManagerEx.makeRootsChange(runnable, false, myModule.isLoaded());
130   }
131
132   public RootModelImpl getRootModel() {
133     return myRootModel;
134   }
135
136   @NotNull
137   @Override
138   public ContentEntry[] getContentEntries() {
139     return myRootModel.getContentEntries();
140   }
141
142   @Override
143   @NotNull
144   public OrderEntry[] getOrderEntries() {
145     return myRootModel.getOrderEntries();
146   }
147
148   @Override
149   public Sdk getSdk() {
150     return myRootModel.getSdk();
151   }
152
153   @Override
154   public boolean isSdkInherited() {
155     return myRootModel.isSdkInherited();
156   }
157
158   void commitModel(RootModelImpl rootModel) {
159     ApplicationManager.getApplication().assertWriteAccessAllowed();
160     LOG.assertTrue(rootModel.myModuleRootManager == this);
161     LOG.assertTrue(!myIsDisposed);
162
163     boolean changed = rootModel.isChanged();
164
165     final Project project = myModule.getProject();
166     final ModifiableModuleModel moduleModel = ModuleManager.getInstance(project).getModifiableModel();
167     ModifiableModelCommitter.multiCommit(Collections.singletonList(rootModel), moduleModel);
168
169     if (changed) {
170       stateChanged();
171     }
172   }
173
174   static void doCommit(RootModelImpl rootModel) {
175     ModuleRootManagerImpl rootManager = (ModuleRootManagerImpl)getInstance(rootModel.getModule());
176     LOG.assertTrue(!rootManager.myIsDisposed);
177     rootModel.docommit();
178     rootModel.dispose();
179
180     try {
181       rootManager.stateChanged();
182     }
183     catch (Exception e) {
184       LOG.error(e);
185     }
186   }
187
188   @Override
189   @NotNull
190   public Module[] getDependencies() {
191     return myRootModel.getModuleDependencies();
192   }
193
194   @NotNull
195   @Override
196   public Module[] getDependencies(boolean includeTests) {
197     return myRootModel.getModuleDependencies(includeTests);
198   }
199
200   @NotNull
201   @Override
202   public Module[] getModuleDependencies() {
203     return myRootModel.getModuleDependencies();
204   }
205
206   @NotNull
207   @Override
208   public Module[] getModuleDependencies(boolean includeTests) {
209     return myRootModel.getModuleDependencies(includeTests);
210   }
211
212   @Override
213   public boolean isDependsOn(@NotNull Module module) {
214     return myRootModel.findModuleOrderEntry(module) != null;
215   }
216
217   @Override
218   @NotNull
219   public String[] getDependencyModuleNames() {
220     return myRootModel.getDependencyModuleNames();
221   }
222
223   @Override
224   public <T> T getModuleExtension(@NotNull final Class<T> klass) {
225     return myRootModel.getModuleExtension(klass);
226   }
227
228   @Override
229   public <R> R processOrder(@NotNull RootPolicy<R> policy, R initialValue) {
230     LOG.assertTrue(!myIsDisposed);
231     return myRootModel.processOrder(policy, initialValue);
232   }
233
234   @NotNull
235   @Override
236   public OrderEnumerator orderEntries() {
237     return new ModuleOrderEnumerator(myRootModel, myOrderRootsCache);
238   }
239
240   public static OrderRootsEnumerator getCachingEnumeratorForType(@NotNull OrderRootType type, @NotNull Module module) {
241     return getEnumeratorForType(type, module).usingCache();
242   }
243
244   @NotNull
245   private static OrderRootsEnumerator getEnumeratorForType(@NotNull OrderRootType type, @NotNull Module module) {
246     OrderEnumerator base = OrderEnumerator.orderEntries(module);
247     if (type == OrderRootType.CLASSES) {
248       return base.exportedOnly().withoutModuleSourceEntries().recursively().classes();
249     }
250     if (type == OrderRootType.SOURCES) {
251       return base.exportedOnly().recursively().sources();
252     }
253     return base.roots(type);
254   }
255
256   @Override
257   @NotNull
258   public VirtualFile[] getContentRoots() {
259     LOG.assertTrue(!myIsDisposed);
260     return myRootModel.getContentRoots();
261   }
262
263   @Override
264   @NotNull
265   public String[] getContentRootUrls() {
266     LOG.assertTrue(!myIsDisposed);
267     return myRootModel.getContentRootUrls();
268   }
269
270   @Override
271   @NotNull
272   public String[] getExcludeRootUrls() {
273     LOG.assertTrue(!myIsDisposed);
274     return myRootModel.getExcludeRootUrls();
275   }
276
277   @Override
278   @NotNull
279   public VirtualFile[] getExcludeRoots() {
280     LOG.assertTrue(!myIsDisposed);
281     return myRootModel.getExcludeRoots();
282   }
283
284   @Override
285   @NotNull
286   public String[] getSourceRootUrls() {
287     return getSourceRootUrls(true);
288   }
289
290   @NotNull
291   @Override
292   public String[] getSourceRootUrls(boolean includingTests) {
293     LOG.assertTrue(!myIsDisposed);
294     return myRootModel.getSourceRootUrls(includingTests);
295   }
296
297   @Override
298   @NotNull
299   public VirtualFile[] getSourceRoots() {
300     return getSourceRoots(true);
301   }
302
303   @Override
304   @NotNull
305   public VirtualFile[] getSourceRoots(final boolean includingTests) {
306     LOG.assertTrue(!myIsDisposed);
307     return myRootModel.getSourceRoots(includingTests);
308   }
309
310   @NotNull
311   @Override
312   public List<VirtualFile> getSourceRoots(@NotNull JpsModuleSourceRootType<?> rootType) {
313     return myRootModel.getSourceRoots(rootType);
314   }
315
316   @NotNull
317   @Override
318   public List<VirtualFile> getSourceRoots(@NotNull Set<? extends JpsModuleSourceRootType<?>> rootTypes) {
319     return myRootModel.getSourceRoots(rootTypes);
320   }
321
322   @Override
323   public void dropCaches() {
324     myOrderRootsCache.clearCache();
325   }
326
327   public ModuleRootManagerState getState() {
328     if (Registry.is("store.track.module.root.manager.changes", false)) {
329       LOG.error("getState, module " + myModule.getName());
330     }
331     return new ModuleRootManagerState(myRootModel);
332   }
333
334   public void loadState(@NotNull ModuleRootManagerState object) {
335     loadState(object, myLoaded || myModule.isLoaded());
336     myLoaded = true;
337   }
338
339   protected void loadState(ModuleRootManagerState object, boolean throwEvent) {
340     ThrowableRunnable<RuntimeException> r = () -> {
341       final RootModelImpl newModel = new RootModelImpl(object.getRootModelElement(), this, myProjectRootManager, myFilePointerManager, throwEvent);
342
343       if (throwEvent) {
344         makeRootsChange(() -> doCommit(newModel));
345       }
346       else {
347         myRootModel.dispose();
348         myRootModel = newModel;
349       }
350
351       assert !myRootModel.isOrderEntryDisposed();
352     };
353     try {
354       if (throwEvent) WriteAction.run(r);
355       else ReadAction.run(r);
356     }
357     catch (InvalidDataException e) {
358       LOG.error(e);
359     }
360   }
361
362   public void stateChanged() {
363     if (Registry.is("store.track.module.root.manager.changes", false)) {
364       LOG.error("ModelRootManager state changed");
365     }
366     myModificationTracker.incModificationCount();
367   }
368
369   @Override
370   @Nullable
371   public ProjectModelExternalSource getExternalSource() {
372     return ExternalProjectSystemRegistry.getInstance().getExternalSource(myModule);
373   }
374
375   public static class ModuleRootManagerState implements JDOMExternalizable {
376     private RootModelImpl myRootModel;
377     private Element myRootModelElement;
378
379     public ModuleRootManagerState() {
380     }
381
382     public ModuleRootManagerState(RootModelImpl rootModel) {
383       myRootModel = rootModel;
384     }
385
386     @Override
387     public void readExternal(Element element) {
388       myRootModelElement = element;
389     }
390
391     @Override
392     public void writeExternal(Element element) {
393       myRootModel.writeExternal(element);
394     }
395
396     public Element getRootModelElement() {
397       return myRootModelElement;
398     }
399   }
400 }